Merge pull request #305 from Qv2ray/dev

Testing version Qv2ray v2.1
This commit is contained in:
Qv2ray-dev 2020-02-11 21:19:47 +08:00 committed by GitHub
commit 56a6dc36bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
185 changed files with 10565 additions and 8598 deletions

View File

@ -1,117 +0,0 @@
name: Qv2ray Push Build for Linux
on:
push:
release:
types: [prereleased]
jobs:
# =================================================================================== LINUX
Linux:
name: Push build for Linux
runs-on: ubuntu-16.04
steps:
- name: Checking out sources
uses: actions/checkout@master
- name: Restoring submodules
run: git submodule update --init
# --------------------------------------------------------
- name: Installing Qt
uses: jurplel/install-qt-action@v2
with:
modules: qtcharts
# --------------------------------------------------------
- name: Install Packages
run: |
sudo add-apt-repository ppa:webispy/grpc
sudo add-apt-repository ppa:carsten-uppenbrink-net/openssl
sudo apt update
sudo apt install -y libgl-dev openssl libx11-dev libxkbcommon-x11-dev libgrpc++-dev libprotobuf-dev protobuf-compiler protobuf-c-compiler protobuf-compiler-grpc
# --------------------------------------------------------
- name: Extracting gRPC and protobuf libs and headers
run: tools/grpc_gen.sh
# --------------------------------------------------------
- name: Download libqvb static library.
uses: Legion2/download-release-action@v2.1.0
with:
repository: Qv2ray/QvRPCBridge
tag: 'latest'
path: libs
file: libqvb-linux64.a
# -------------------------------------------------------- libqvb build
- name: libqvb - Build Qv2ray
run: |
mkdir build
cd build
qmake .. CONFIG+=no_increase_build_number CONFIG+=with_new_backend PREFIX=/usr
make -j2
- name: libqvb - Generating filesystem structure for AppImage
run: |
cd build
make install INSTALL_ROOT=AppDir
cd AppDir
mkdir -p ./usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ./usr/lib/
- name: libqvb - Building AppImage using linuxdeployqt
run: |
cd build
wget https://github.com/probonopd/linuxdeployqt/releases/download/6/linuxdeployqt-6-x86_64.AppImage
chmod +x ./linuxdeployqt-6-x86_64.AppImage
./linuxdeployqt-6-x86_64.AppImage --appimage-extract
cd AppDir
../squashfs-root/AppRun usr/share/applications/qv2ray.desktop -appimage -no-strip -always-overwrite
mv ./Qv2ray*.AppImage ./Qv2ray.AppImage
- name: libqvb - Uploading artifact
uses: actions/upload-artifact@master
with:
name: Qv2ray.linux-libqvb.AppImage
path: build/AppDir/Qv2ray.AppImage
- name: libqvb - Upload binaries to release
if: github.event_name == 'release'
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/AppDir/Qv2ray.AppImage
asset_name: Qv2ray-${{ github.ref }}-linux-libqvb.AppImage
tag: ${{ github.ref }}
overwrite: true
# -------------------------------------------------------- cleanup build
- name: cleanup -- clean up build directory to build against legacy gRPC
run: rm -rvf ./build
# -------------------------------------------------------- gRPC build
- name: gRPC - Build Qv2ray
run: |
mkdir build
cd build
qmake .. CONFIG+=no_increase_build_number PREFIX=/usr
make -j2
- name: gRPC - Generating filesystem structure for AppImage
run: |
cd build
make install INSTALL_ROOT=AppDir
cd AppDir
mkdir -p ./usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ./usr/lib/
- name: gRPC - Building AppImage using linuxdeployqt
run: |
cd build
wget https://github.com/probonopd/linuxdeployqt/releases/download/6/linuxdeployqt-6-x86_64.AppImage
chmod +x ./linuxdeployqt-6-x86_64.AppImage
./linuxdeployqt-6-x86_64.AppImage --appimage-extract
cd AppDir
../squashfs-root/AppRun usr/share/applications/qv2ray.desktop -appimage -no-strip -always-overwrite
mv ./Qv2ray*.AppImage ./Qv2ray.AppImage
- name: gRPC - Uploading artifact
uses: actions/upload-artifact@master
with:
name: Qv2ray.linux-gRPC.AppImage
path: build/AppDir/Qv2ray.AppImage
- name: gRPC - Upload binaries to release
if: github.event_name == 'release'
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/AppDir/Qv2ray.AppImage
asset_name: Qv2ray-${{ github.ref }}-linux.gRPC.AppImage
tag: ${{ github.ref }}
overwrite: true

View File

@ -1,111 +0,0 @@
name: Qv2ray Push Build for Windows
on:
push:
release:
types: [prereleased]
jobs:
# ======================================================================================== WINDOWS
Windows:
name: Push build for win64
runs-on: windows-latest
steps:
- name: Checking out Qv2ray sources
uses: actions/checkout@master
- name: Restoring submodules
run: git submodule update --init
# --------------------------------------------------------
- name: Installing Qt
uses: jurplel/install-qt-action@v2
with:
arch: win64_mingw73
modules: qtcharts
# --------------------------------------------------------
- name: Extracting gRPC and protobuf libs and headers
run: tools\grpc_gen.bat
# --------------------------------------------------------
- name: Download libqvb static library.
run: curl -o .\libs\libqvb-win64.a -L https://github.com/Qv2ray/QvRPCBridge/releases/download/v1.1/libqvb-win64.a
# -------------------------------------------------------- libqvb
- name: libqvb - Building Qv2ray
shell: cmd
run: |
mkdir build
cd build
qmake .. CONFIG+=no_increase_build_number CONFIG+=with_new_backend
mingw32-make -j2
- name: libqvb - Copying DLLs to build output folders
shell: cmd
run: .github\workflows\copy_DLLs.bat
- name: libqvb - Deploy Qt
shell: cmd
run: |
cd build
cd release
del *.cpp *.h *.o *.qrc *.qm
windeployqt ./qv2ray.exe --compiler-runtime
- name: libqvb - Create 7z Release
uses: DuckSoft/create-7z-action@v1.0
with:
# file/folder path to compress
pathSource: ./build/release/
# 7z archive path to write
pathTarget: ./release.7z
- name: libqvb - Uploading artifact
uses: actions/upload-artifact@master
with:
name: Qv2ray.Win64-libqvb.7z
path: release.7z
- name: libqvb - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: release.7z
asset_name: Qv2ray-${{ github.ref }}-win64-libqvb.7z
tag: ${{ github.ref }}
overwrite: true
# -------------------------------------------------------- cleanup
- name: cleanup -- clean up build directory to build against legacy gRPC
shell: cmd
run: echo y|rmdir /S build
# -------------------------------------------------------- gRPC
- name: gRPC - Building Qv2ray
shell: cmd
run: |
mkdir build
cd build
qmake .. CONFIG+=no_increase_build_number CONFIG+=with_new_backend
mingw32-make -j2
- name: gRPC - Copying DLLs to build output folders
shell: cmd
run: .github\workflows\copy_DLLs.bat
- name: gRPC - Deploy Qt
shell: cmd
run: |
cd build
cd release
del *.cpp *.h *.o *.qrc *.qm
windeployqt ./qv2ray.exe --compiler-runtime
- name: gRPC - Create 7z Release
uses: DuckSoft/create-7z-action@v1.0
with:
# file/folder path to compress
pathSource: ./build/release/
# 7z archive path to write
pathTarget: ./release.7z
- name: gRPC - Uploading artifact
uses: actions/upload-artifact@master
with:
name: Qv2ray.Win64-gRPC.7z
path: release.7z
- name: gRPC - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: release.7z
asset_name: Qv2ray-${{ github.ref }}-win64-gRPC.7z
tag: ${{ github.ref }}
overwrite: true

19
.github/workflows/aur.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: AUR Test Build
on:
push:
paths-ignore: [README.md]
release: {}
schedule:
- cron: "0 19 * * *"
jobs:
aur:
strategy:
fail-fast: true
matrix:
repo: [qv2ray, qv2ray-dev-git]
runs-on: [ubuntu-latest]
steps:
- uses: DuckSoft/build-aur-action@v1.0
with:
repo-name: ${{ matrix.repo }}

178
.github/workflows/build-qv2ray.yml vendored Normal file
View File

@ -0,0 +1,178 @@
name: Qv2ray build matrix
on:
push:
release:
types: [prereleased]
jobs:
build:
strategy:
matrix:
qt_version: [5.12.6, 5.13.2, 5.14.1]
platform: [ubuntu-16.04, macos-latest, windows-latest]
arch: [x86, x64]
exclude:
- platform: ubuntu-16.04
arch: x86
- platform: macos-latest
arch: x86
fail-fast: false
runs-on: ${{ matrix.platform }}
steps:
- name: Get the version
id: get_version
shell: bash
run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3)
- name: Checking out sources
uses: actions/checkout@master
- name: Restoring submodules
run: git submodule update --init
# --------------------------------------------------------
- name: Install MSVC compiler
if: matrix.platform == 'windows-latest'
uses: ilammy/msvc-dev-cmd@v1
with:
# 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo
toolset: 14.2
arch: ${{ matrix.arch }}
- name: Installing Qt - ${{ matrix.arch }}
uses: Qv2ray/install-qt-action@master
with:
version: ${{ matrix.qt_version }}
arch: ${{ matrix.arch }}
# --------------------------------------------------------
- name: Linux - Install Packages
if: matrix.platform == 'ubuntu-16.04'
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo add-apt-repository ppa:webispy/grpc
sudo add-apt-repository ppa:carsten-uppenbrink-net/openssl
sudo apt update
sudo apt install -y gcc-7 libgl-dev openssl libx11-dev libxkbcommon-x11-dev libprotobuf-dev protobuf-compiler protobuf-c-compiler
- name: macOS - Install Packages
if: matrix.platform == 'macos-latest'
run: brew install protobuf
# --------------------------------------------------------
- name: Cross-platform - Download libraries
shell: bash
run: |
curl -o ./libs/libqvb-linux64.a -L https://github.com/Qv2ray/QvRPCBridge/releases/download/v1.1/libqvb-linux64.a
curl -o ./libs/libqvb-darwin.a -L https://github.com/Qv2ray/QvRPCBridge/releases/download/v1.1/libqvb-darwin.a
curl -o ./libs/Qv2ray-deps-${{ matrix.arch }}-windows.7z -L https://github.com/Qv2ray/Qv2ray-deps/releases/download/release/Qv2ray-deps-${{ matrix.arch }}-windows.7z
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Extract Windows Dependencies
if: matrix.platform == 'windows-latest'
uses: DuckSoft/extract-7z-action@v1.0
with:
pathSource: ./libs/Qv2ray-deps-${{ matrix.arch }}-windows.7z
pathTarget: ./libs
# -------------------------------------------------------- Generate MakeFile
- name: Cross-platform - ${{ matrix.qt_version }} - Generate Dependencies and Makefile
shell: bash
run: |
mkdir build
cd build
export _QV2RAY_BUILD_INFO_="Qv2ray built from Github Action"
export _QV2RAY_BUILD_EXTRA_INFO_="qt${{ matrix.qt_version }}-${{ github.sha }}"
qmake .. CONFIG+=no_increase_build_number PREFIX=/usr
# -------------------------------------------------------- Build
- name: Unix - ${{ matrix.qt_version }} - Build Qv2ray
if: matrix.platform != 'windows-latest'
run: |
cd build
make -j2 $([ "${{ matrix.platform }}" == "ubuntu-16.04" ] && echo "CC=gcc-7 CXX=g++-7" LINK="g++-7" || echo "")
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Build Qv2ray
if: matrix.platform == 'windows-latest'
run: |
cd build
nmake
# -------------------------------------------------------- Deployments
- name: Linux - ${{ matrix.qt_version }} - Generating AppImage
if: matrix.platform == 'ubuntu-16.04'
run: |
cd build
wget https://github.com/probonopd/linuxdeployqt/releases/download/6/linuxdeployqt-6-x86_64.AppImage
chmod +x ./linuxdeployqt-6-x86_64.AppImage
./linuxdeployqt-6-x86_64.AppImage --appimage-extract
make install INSTALL_ROOT=AppDir
cd AppDir
mkdir -p ./usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ./usr/lib/
../squashfs-root/AppRun usr/share/applications/qv2ray.desktop -appimage -no-strip -always-overwrite
mv ./Qv2ray*.AppImage ./Qv2ray.AppImage
- name: Linux - ${{ matrix.qt_version }} - Uploading artifact
if: matrix.platform == 'ubuntu-16.04'
uses: actions/upload-artifact@master
with:
name: Qv2ray-${{ github.sha }}.linux-${{ matrix.arch }}.qt${{ matrix.qt_version }}.AppImage
path: build/AppDir/Qv2ray.AppImage
- name: Linux - ${{ matrix.qt_version }} - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release' && matrix.platform == 'ubuntu-16.04'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/AppDir/Qv2ray.AppImage
asset_name: Qv2ray.${{ steps.get_version.outputs.VERSION }}.linux-${{ matrix.arch }}.qt${{ matrix.qt_version }}.AppImage
tag: ${{ github.ref }}
overwrite: true
# ==
- name: macOS - ${{ matrix.qt_version }} - Making release tarball
if: matrix.platform == 'macos-latest'
run: |
cd build
cd qv2ray.app
macdeployqt ./
cd ..
tar czf Qv2ray.app.tar.gz qv2ray.app
- name: macOS - ${{ matrix.qt_version }} - Uploading Artifact
if: matrix.platform == 'macos-latest'
uses: actions/upload-artifact@master
with:
name: Qv2ray-${{ github.sha }}.macOS-${{ matrix.arch }}.qt${{ matrix.qt_version }}.zip
path: build/Qv2ray.app.tar.gz
- 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'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/Qv2ray.app.tar.gz
asset_name: Qv2ray.${{ steps.get_version.outputs.VERSION }}.macOS-${{ matrix.arch }}.qt${{ matrix.qt_version }}.tar.gz
tag: ${{ github.ref }}
overwrite: true
# ==
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Copying DLLs to build output folders
if: matrix.platform == 'windows-latest'
run: .\.github\workflows\copy_DLLs.bat
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Running windeployqt
if: matrix.platform == 'windows-latest'
shell: cmd
run: |
cd build
cd release
del *.cpp *.h *.o *.qrc *.qm *.hpp *.obj
set VCINSTALLDIR=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\
windeployqt ./qv2ray.exe --compiler-runtime --verbose 2
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Create 7z Release
if: matrix.platform == 'windows-latest'
uses: DuckSoft/create-7z-action@v1.0
with:
pathSource: ./build/release/
pathTarget: ./release.7z
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Uploading artifact
if: matrix.platform == 'windows-latest'
uses: actions/upload-artifact@master
with:
name: Qv2ray-${{ github.sha }}.Windows-${{ matrix.arch }}.qt${{ matrix.qt_version }}.7z
path: release.7z
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release' && matrix.platform == 'windows-latest'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: release.7z
asset_name: Qv2ray.${{ steps.get_version.outputs.VERSION }}.Windows-${{ matrix.arch }}.qt${{ matrix.qt_version }}.7z
tag: ${{ github.ref }}
overwrite: true

View File

@ -1,5 +1,5 @@
@echo off @echo off
echo Copying Windows Necessary DLLs echo Copying DLLs for Windows
forfiles /s /p %GITHUB_WORKSPACE%\libs\ /m "*.dll" /c "cmd.exe /c copy @file %GITHUB_WORKSPACE%\build\release\" forfiles /s /p %GITHUB_WORKSPACE%\libs\x86-windows\ /m "*.dll" /c "cmd.exe /c copy @file %GITHUB_WORKSPACE%\build\release\"
forfiles /s /p %GITHUB_WORKSPACE%\libs\ /m "*.dll" /c "cmd.exe /c copy @file %GITHUB_WORKSPACE%\build\debug\" forfiles /s /p %GITHUB_WORKSPACE%\libs\x64-windows\ /m "*.dll" /c "cmd.exe /c copy @file %GITHUB_WORKSPACE%\build\release\"
exit 0 exit 0

View File

@ -1,87 +0,0 @@
name: Qv2ray Push Build for macOS
on:
push:
release:
types: [prereleased]
jobs:
macOS:
name: Push build for macOS
runs-on: macos-latest
steps:
- name: Checking out sources
uses: actions/checkout@master
- name: Restoring submodules
run: git submodule update --init
# --------------------------------------------------------
- name: Installing Qt
uses: jurplel/install-qt-action@v2
with:
modules: qtcharts
# --------------------------------------------------------
- name: Extracting gRPC and protobuf libs and headers
run: tools/deps_macOS.sh
# --------------------------------------------------------
- name: Download libqvb static library.
run: curl -o ./libs/libqvb-darwin.a -L https://github.com/Qv2ray/QvRPCBridge/releases/download/v1.1/libqvb-darwin.a
# -------------------------------------------------------- == build against libqvb
- name: libqvb - Build Qv2ray
run: |
mkdir build
cd build
qmake .. CONFIG+=no_increase_build_number CONFIG+=with_new_backend
make -j2
- name: libqvb - Making release tarball
run: |
cd build
cd qv2ray.app
macdeployqt ./
cd ..
tar czf Qv2ray.app.tar.gz qv2ray.app
- name: libqvb - Uploading Artifact
uses: actions/upload-artifact@master
with:
name: Qv2ray.macOS.app-libqvb.zip
path: build/Qv2ray.app.tar.gz
- name: libqvb - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/Qv2ray.app.tar.gz
asset_name: Qv2ray-${{ github.ref }}-macOS-libqvb.tar.gz
tag: ${{ github.ref }}
overwrite: true
# -------------------------------------------------------- == cleanup
- name: cleanup -- clean up build directory to build against legacy gRPC
run: rm -rvf ./build
# -------------------------------------------------------- == build against gRPC
- name: gRPC - Build Qv2ray
run: |
mkdir build
cd build
qmake .. CONFIG+=no_increase_build_number
make -j2
- name: gRPC - Making release tarball
run: |
cd build
cd qv2ray.app
macdeployqt ./
cd ..
tar czf Qv2ray.app.tar.gz qv2ray.app
- name: gRPC - Uploading Artifact
uses: actions/upload-artifact@master
with:
name: Qv2ray.macOS.app-gRPC.zip
path: build/Qv2ray.app.tar.gz
- name: gRPC - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release'
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/Qv2ray.app.tar.gz
asset_name: Qv2ray-${{ github.ref }}-macOS-gRPC.tar.gz
tag: ${{ github.ref }}
overwrite: true

2
.gitignore vendored
View File

@ -7,6 +7,8 @@
SourceTrail/ SourceTrail/
libs/gen/ libs/gen/
libs/x64-windows/
libs/x86-windows/
libs/libqvb-* libs/libqvb-*
build/ build/

3
.gitmodules vendored
View File

@ -13,9 +13,6 @@
[submodule "3rdparty/qzxing"] [submodule "3rdparty/qzxing"]
path = 3rdparty/qzxing path = 3rdparty/qzxing
url = https://github.com/ftylitak/qzxing url = https://github.com/ftylitak/qzxing
[submodule "libs/gRPC-win32"]
path = libs/gRPC-win32
url = https://github.com/Qv2ray/gRPC-Windows-Packages
[submodule "libs/libqvb"] [submodule "libs/libqvb"]
path = libs/libqvb path = libs/libqvb
url = https://github.com/Qv2ray/QvRPCBridge url = https://github.com/Qv2ray/QvRPCBridge

37
.travis.yml Normal file
View File

@ -0,0 +1,37 @@
language: bash
dist: bionic
git:
depth: false
branches:
only:
- dev
env:
global:
- LC_ALL: C.UTF-8
- LANG: C.UTF-8
addons:
snaps:
- name: snapcraft
channel: stable
confinement: classic
- name: lxd
channel: stable
script:
- sudo /snap/bin/lxd.migrate -yes
- 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

@ -1 +1 @@
Subproject commit 3d152b03d872b99d7f36e8463ac253e41194a17a Subproject commit 0f6695e2a9d8fdaa336e7ad941855c46c61f218a

2
3rdparty/qzxing vendored

@ -1 +1 @@
Subproject commit 2a58c5032b2180f2cce95e1db106cbaa4ecaed02 Subproject commit f0a78867d697fd271528007c7b4c67e8fce75f90

2
3rdparty/x2struct vendored

@ -1 +1 @@
Subproject commit 09d790b83155336acb73730ac241fdc4256a85bd Subproject commit 4539764671509655370b2ff4fae90bfd8a12df40

View File

@ -1 +0,0 @@
3100

View File

@ -4,140 +4,17 @@
# #
#------------------------------------------------- #-------------------------------------------------
QT += core gui widgets network charts QT += core gui widgets network
TARGET = qv2ray TARGET = qv2ray
TEMPLATE = app TEMPLATE = app
# Now read build number file. # Now read build number file.
_BUILD_NUMBER=$$cat(Build.Counter) QV2RAY_VERSION=$$cat($$PWD/makespec/VERSION)
VERSION = 2.0.1.$$_BUILD_NUMBER QV2RAY_BUILD_VERSION=$$cat($$PWD/makespec/BUILDVERSION)
no_increase_build_number { VERSION = $${QV2RAY_VERSION}.$${QV2RAY_BUILD_VERSION}
message("Build.Counter will not be increased")
} else {
_BUILD_NUMBER = $$num_add($$_BUILD_NUMBER, 1)
write_file("Build.Counter", _BUILD_NUMBER)
}
# Unix (Actually Linux only) prefix config.
isEmpty(PREFIX) {
PREFIX=/usr/local
}
message("Qv2ray installation PREFIX="$$PREFIX)
DEFINES += QT_DEPRECATED_WARNINGS QV2RAY_VERSION_STRING=\"\\\"v$${VERSION}\\\"\" QAPPLICATION_CLASS=QApplication
# Don't merge those configs with below.
CONFIG += enable_decoder_qr_code enable_encoder_qr_code qt c++11 openssl-linked
include(3rdparty/qzxing/src/QZXing-components.pri)
include(3rdparty/SingleApplication/singleapplication.pri)
include(3rdparty/QNodeEditor/QNodeEditor.pri)
include(3rdparty/x2struct/x2struct.pri)
# Main config
CONFIG += lrelease embed_translations
# Win32 support.
win32:CONFIG += win
win64:CONFIG += win
SOURCES += \
src/components/QvComponentsHandler.cpp \
src/components/QvCore/QvCommandLineArgs.cpp \
src/components/QvKernelInteractions.cpp \
src/components/QvLaunchAtLoginConfigurator.cpp \
src/components/QvPACHandler.cpp \
src/components/QvSystemProxyConfigurator.cpp \
src/components/QvTCPing.cpp \
src/main.cpp \
src/components/QvGFWPACConverter.cpp \
src/components/QvHTTPRequestHelper.cpp \
src/components/QvLogHighlighter.cpp \
src/QvCoreConfigOperations.cpp \
src/QvConfigUpgrade.cpp \
src/QvCoreConfigOperations_Convertion.cpp \
src/QvCoreConfigOperations_Generation.cpp \
src/QvUtils.cpp \
src/ui/routeNodeModels/QvInboundNodeModel.cpp \
src/ui/routeNodeModels/QvOutboundNodeModel.cpp \
src/ui/routeNodeModels/QvRuleNodeModel.cpp \
src/ui/w_MainWindow_extra.cpp \
src/ui/w_PreferencesWindow.cpp \
src/ui/w_RoutesEditor_extra.cpp \
src/utils/QvHelpers.cpp \
src/utils/QJsonModel.cpp \
src/ui/w_ExportConfig.cpp \
src/ui/w_InboundEditor.cpp \
src/ui/w_OutboundEditor.cpp \
src/ui/w_RoutesEditor.cpp \
src/ui/w_SubscriptionEditor.cpp \
src/ui/w_JsonEditor.cpp \
src/ui/w_MainWindow.cpp \
src/ui/w_ImportConfig.cpp \
src/ui/w_ScreenShot_Core.cpp \
src/ui/NetSpeedBar/QvNetSpeedBar.cpp
INCLUDEPATH += \
3rdparty/ \
src/ \
src/components \
src/ui/ \
src/utils/ \
libs/gen/
HEADERS += \
src/Qv2rayBase.hpp \
src/Qv2rayFeatures.hpp \
src/QvCoreConfigObjects.hpp \
src/QvCoreConfigOperations.hpp \
src/QvUtils.hpp \
src/components/QvComponentsHandler.hpp \
src/components/QvCore/QvCommandLineArgs.hpp \
src/components/QvHTTPRequestHelper.hpp \
src/components/QvKernelInteractions.hpp \
src/components/QvLaunchAtLoginConfigurator.hpp \
src/components/QvLogHighlighter.hpp \
src/components/QvNetSpeedPlugin.hpp \
src/components/QvPACHandler.hpp \
src/components/QvSystemProxyConfigurator.hpp \
src/components/QvTCPing.hpp \
src/ui/routeNodeModels/QvInboundNodeModel.hpp \
src/ui/routeNodeModels/QvNodeModelsBase.hpp \
src/ui/routeNodeModels/QvOutboundNodeModel.hpp \
src/ui/routeNodeModels/QvRuleNodeModel.hpp \
src/ui/w_ExportConfig.hpp \
src/ui/w_ImportConfig.hpp \
src/ui/w_InboundEditor.hpp \
src/ui/w_JsonEditor.hpp \
src/ui/w_MainWindow.hpp \
src/ui/w_OutboundEditor.hpp \
src/ui/w_PreferencesWindow.hpp \
src/ui/w_RoutesEditor.hpp \
src/ui/w_SubscriptionEditor.hpp \
src/ui/w_ScreenShot_Core.hpp \
src/utils/QvHelpers.hpp \
src/utils/QvTinyLog.hpp \
src/utils/QJsonModel.hpp \
src/utils/QJsonObjectInsertMacros.h
FORMS += \
src/ui/w_ExportConfig.ui \
src/ui/w_ImportConfig.ui \
src/ui/w_InboundEditor.ui \
src/ui/w_JsonEditor.ui \
src/ui/w_MainWindow.ui \
src/ui/w_OutboundEditor.ui \
src/ui/w_PreferencesWindow.ui \
src/ui/w_RoutesEditor.ui \
src/ui/w_ScreenShot_Core.ui \
src/ui/w_SubscriptionEditor.ui
RESOURCES += \
resources.qrc
# Fine......
message(" ") message(" ")
message("Qv2ray Version: $${VERSION}") message("Qv2ray Version: $${VERSION}")
message("|-------------------------------------------------|") message("|-------------------------------------------------|")
@ -149,183 +26,143 @@ message("| permitted by local law. |")
message("| |") message("| |")
message("| See: https://www.gnu.org/licenses/gpl-3.0.html |") message("| See: https://www.gnu.org/licenses/gpl-3.0.html |")
message("|-------------------------------------------------|") message("|-------------------------------------------------|")
message("| Project Homepage: https://github.com/Qv2ray |")
message("| Welcome to contribute! |")
message("|-------------------------------------------------|")
message(" ") message(" ")
# Distinguish debug and release builds.
RC_ICONS += ./assets/icons/qv2ray.ico CONFIG(release, debug|release) {
ICON = ./assets/icons/qv2ray.icns CONFIG+=Qv2ray_release no_increase_build_number
}
with_new_backend { CONFIG(debug, debug|release) {
!exists($$PWD/libs/libqvb/build/libqvb.h) { CONFIG+=Qv2ray_debug
message(" ")
message("Cannot continue: ")
message(" --> Qv2ray is configured to use custom backend, but: ")
message(" libs/libqvb/build/libqvb.h is missing. ")
error("! ABORTING THE BUILD !")
message(" ")
}
message("Qv2ray will use custom API backend.")
message(" --> Adding libqvb header.")
HEADERS += libs/libqvb/build/libqvb.h
# ==-- OS Specific configurations for libqvb --==
win {
message(" --> Linking libqvb static library, for Windows platform.")
LIBS += -L$$PWD/libs/ -lqvb-win64
}
unix:!macx {
message(" --> Linking libqvb static library, for Linux platform.")
LIBS += -L$$PWD/libs/ -lqvb-linux64
}
macx {
message(" --> Linking libqvb static library and Security framework, for macOS platform.")
LIBS += -L$$PWD/libs/ -lqvb-darwin
LIBS += -framework Security
}
} else {
DEFINES += WITH_LIB_GRPCPP
message("Qv2ray will use libgRPC as API backend")
# ------------------------------------------ Begin checking gRPC and protobuf headers.
!exists($$PWD/libs/gen/v2ray_api_commands.grpc.pb.h) || !exists($$PWD/libs/gen/v2ray_api_commands.grpc.pb.cc) || !exists($$PWD/libs/gen/v2ray_api_commands.pb.h) || !exists($$PWD/libs/gen/v2ray_api_commands.pb.cc) {
message(" ")
message("-----------------------------------------------")
message("Cannot continue: ")
message(" --> Qv2ray is not properly configured yet: ")
message(" gRPC and protobuf headers for v2ray API is missing.")
message(" --> Please run gen_grpc.sh gen_grpc.bat or deps_macOS.sh located in tools/")
message(" --> Or consider reading the build wiki: https://github.com/lhy0403/Qv2ray/wiki/Manually-Build-Qv2ray")
message("-----------------------------------------------")
message(" ")
warning("IF YOU THINK IT'S A MISTAKE, PLEASE OPEN AN ISSUE")
error("! ABORTING THE BUILD !")
message(" ")
}
SOURCES += libs/gen/v2ray_api_commands.pb.cc \
libs/gen/v2ray_api_commands.grpc.pb.cc
HEADERS += libs/gen/v2ray_api_commands.pb.h \
libs/gen/v2ray_api_commands.grpc.pb.h
# ==-- OS Specific configurations for libgRPC and libprotobuf --==
win {
# A hack for protobuf header.
message(" --> Applying a hack for protobuf header")
DEFINES += _WIN32_WINNT=0x600
message(" --> Linking against gRPC and protobuf library.")
DEPENDPATH += $$PWD/libs/gRPC-win32/include
INCLUDEPATH += $$PWD/libs/gRPC-win32/include
LIBS += -L$$PWD/libs/gRPC-win32/lib/ \
-llibprotobuf.dll \
-llibgrpc++.dll
}
unix {
# For gRPC and protobuf in linux and macOS
message(" --> Linking against gRPC and protobuf library.")
LIBS += -L/usr/local/lib -lgrpc++ -lprotobuf -lgrpc
}
macx {
message(" --> Linking libgpr and libupb.")
LIBS += -lgpr -lupb
}
} }
message(" ") # Qv2ray basic configuration
# ------------------------------------------ Begin to detect language files. CONFIG += qt c++17 openssl-linked
message("Looking for language support.") include($$PWD/makespec/00-deps.pri)
QM_FILES_RESOURCE_PREFIX = "translations"
for(var, $$list($$files("translations/*.ts", true))) {
LOCALE_FILENAME = $$basename(var)
message(" --> Found:" $$LOCALE_FILENAME)
!equals(LOCALE_FILENAME, "en_US.ts") {
# ONLY USED IN LRELEASE CONTEXT
# en_US is not EXTRA...
EXTRA_TRANSLATIONS += translations/$$LOCALE_FILENAME
}
}
message("Qv2ray will build with" $${replace(EXTRA_TRANSLATIONS, "translations/", "")})
TRANSLATIONS += translations/en_US.ts
message(" ") # lrelease will not work when adding BEFORE 00-deps.pri
QMAKE_CXXFLAGS += -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable CONFIG += lrelease embed_translations
DEFINES += QT_DEPRECATED_WARNINGS QV2RAY_VERSION_STRING=\"\\\"v$${VERSION}\\\"\" QAPPLICATION_CLASS=QApplication
message("Adding QHttpServer Support") # Source file parser
message(" --> Adding qhttpserver") include($$PWD/makespec/01-sourcesparser.pri)
HEADERS += \
$$PWD/3rdparty/qhttpserver/src/qhttpconnection.h \
$$PWD/3rdparty/qhttpserver/src/qhttprequest.h \
$$PWD/3rdparty/qhttpserver/src/qhttpresponse.h \
$$PWD/3rdparty/qhttpserver/src/qhttpserver.h \
$$PWD/3rdparty/qhttpserver/src/qhttpserverapi.h \
$$PWD/3rdparty/qhttpserver/src/qhttpserverfwd.h
SOURCES += \
$$PWD/3rdparty/qhttpserver/src/qhttpconnection.cpp \
$$PWD/3rdparty/qhttpserver/src/qhttprequest.cpp \
$$PWD/3rdparty/qhttpserver/src/qhttpresponse.cpp \
$$PWD/3rdparty/qhttpserver/src/qhttpserver.cpp
INCLUDEPATH += 3rdparty/qhttpserver/src/ # Main config
Qv2rayAddSource(base, _, GlobalInstances, hpp)
Qv2rayAddSource(base, _, JsonHelpers, hpp)
Qv2rayAddSource(base, _, Qv2rayBase, hpp)
Qv2rayAddSource(base, _, Qv2rayFeatures, hpp)
Qv2rayAddSource(base, _, Qv2rayLog, cpp, hpp)
Qv2rayAddSource(base, models, CoreObjectModels, hpp)
Qv2rayAddSource(base, models, QvConfigModel, hpp)
Qv2rayAddSource(base, models, QvConfigIdentifier, hpp)
Qv2rayAddSource(base, models, QvSafeType, hpp)
Qv2rayAddSource(base, models, QvRuntimeConfig, hpp)
Qv2rayAddSource(base, models, QvStartupConfig, hpp)
Qv2rayAddSource(common, _, CommandArgs, cpp, hpp)
Qv2rayAddSource(common, _, HTTPRequestHelper, cpp, hpp)
Qv2rayAddSource(common, _, LogHighlighter, cpp, hpp)
Qv2rayAddSource(common, _, QJsonModel, cpp, hpp)
Qv2rayAddSource(common, _, QvHelpers, cpp, hpp)
Qv2rayAddSource(common, _, QvTranslator, hpp)
Qv2rayAddSource(components, autolaunch, QvAutoLaunch, cpp, hpp)
Qv2rayAddSource(components, pac, QvGFWPACConverter, cpp)
Qv2rayAddSource(components, pac, QvPACHandler, cpp, hpp)
Qv2rayAddSource(components, plugins/toolbar, QvToolbar, cpp, hpp)
Qv2rayAddSource(components, plugins/toolbar, QvToolbar_linux, cpp)
Qv2rayAddSource(components, plugins/toolbar, QvToolbar_win, cpp)
Qv2rayAddSource(components, proxy, QvProxyConfigurator, cpp, hpp)
Qv2rayAddSource(components, tcping, QvTCPing, cpp, hpp)
Qv2rayAddSource(components, speedchart, speedwidget, cpp, hpp)
Qv2rayAddSource(components, speedchart, speedplotview, cpp, hpp)
Qv2rayAddSource(components, geosite, QvGeositeReader, cpp, hpp)
Qv2rayAddSource(core, config, ConfigBackend, cpp, hpp)
Qv2rayAddSource(core, config, ConfigUpgrade, cpp)
Qv2rayAddSource(core, connection, ConnectionIO, cpp, hpp)
Qv2rayAddSource(core, connection, Generation, cpp, hpp)
Qv2rayAddSource(core, connection, Serialization, cpp, hpp)
Qv2rayAddSource(core, _, CoreUtils, cpp, hpp)
Qv2rayAddSource(core, kernel, KernelInteractions, cpp, hpp)
Qv2rayAddSource(core, kernel, APIBackend, cpp, hpp)
Qv2rayAddSource(ui, editors, w_InboundEditor, cpp, hpp, ui)
Qv2rayAddSource(ui, editors, w_JsonEditor, cpp, hpp, ui)
Qv2rayAddSource(ui, editors, w_OutboundEditor, cpp, hpp, ui)
Qv2rayAddSource(ui, editors, w_RoutesEditor, cpp, hpp, ui)
Qv2rayAddSource(ui, editors, w_RoutesEditor_extra, cpp)
Qv2rayAddSource(ui, nodemodels, InboundNodeModel, cpp, hpp)
Qv2rayAddSource(ui, nodemodels, OutboundNodeModel, cpp, hpp)
Qv2rayAddSource(ui, nodemodels, RuleNodeModel, cpp, hpp)
Qv2rayAddSource(ui, nodemodels, NodeModelsBase, hpp)
Qv2rayAddSource(ui, messaging, QvMessageBus, cpp, hpp)
Qv2rayAddSource(ui, _, w_ExportConfig, cpp, hpp, ui)
Qv2rayAddSource(ui, _, w_ImportConfig, cpp, hpp, ui)
Qv2rayAddSource(ui, _, w_MainWindow, cpp, hpp, ui)
Qv2rayAddSource(ui, _, w_MainWindow_extra, cpp)
Qv2rayAddSource(ui, _, w_PreferencesWindow, cpp, hpp, ui)
Qv2rayAddSource(ui, _, w_ScreenShot_Core, cpp, hpp, ui)
Qv2rayAddSource(ui, _, w_SubscriptionManager, cpp, hpp, ui)
Qv2rayAddSource(ui, widgets, StreamSettingsWidget, cpp, hpp, ui)
message(" --> Adding http parser") SOURCES += $$PWD/src/main.cpp
HEADERS += 3rdparty/qhttpserver/http-parser/http_parser.h HEADERS +=
SOURCES += 3rdparty/qhttpserver/http-parser/http_parser.c FORMS +=
INCLUDEPATH += 3rdparty/qhttpserver/http-parser/ INCLUDEPATH += $$PWD/src
RESOURCES += $$PWD/resources.qrc
ICON = $$PWD/assets/icons/qv2ray.icns
RC_ICONS += $$PWD/assets/icons/qv2ray.ico
message(" ") include($$PWD/makespec/02-translations.pri)
win {
message("Configuring for win32 environment")
DEFINES += QHTTPSERVER_EXPORT
message(" --> Setting up target descriptions")
QMAKE_TARGET_DESCRIPTION = "Qv2ray, a cross-platform v2ray GUI client."
QMAKE_TARGET_PRODUCT = "Qv2ray"
message(" --> Adding Taskbar Toolbox CPP files.")
SOURCES += src/ui/NetSpeedBar/QvNetSpeedBar_win.cpp
message(" --> Linking against winHTTP and winSock2.")
LIBS += -lwinhttp -lwininet -lws2_32
}
macx {
# For Linux and macOS
message("Configuring for macOS specific environment")
LIBS += -framework Carbon -framework Cocoa
}
# Reuse unix for macx as well
unix { unix {
# For Linux and macOS include($$PWD/makespec/03-unix.pri)
message("Configuring for unix-like environment") # Sub-process of Qv2ray per-OS build
# macOS homebrew include path !macx: include($$PWD/makespec/04-unix-linux.pri)
message(" --> Adding local include folder to search path") macx: include($$PWD/makespec/04-unix-macOS.pri)
INCLUDEPATH += /usr/local/include/ } else {
include($$PWD/makespec/03-Windows.pri)
message(" --> Adding Plasma Toolbox CPP files.")
SOURCES += $$PWD/src/ui/NetSpeedBar/QvNetSpeedBar_linux.cpp
message(" --> Generating desktop dependency.")
desktop.files += ./assets/qv2ray.desktop
desktop.path = $$PREFIX/share/applications/
message(" --> Generating icons dependency.")
icon.files += ./assets/icons/qv2ray.png
icon.path = $$PREFIX/share/icons/hicolor/256x256/apps/
target.path = $$PREFIX/bin/
INSTALLS += target desktop icon
} }
with_metainfo { # ------------------------------------------ Begin checking protobuf domain list headers.
message(" --> Generating metainfo dependency.") !exists($$PWD/libs/gen/v2ray_geosite.pb.cc) || !exists($$PWD/libs/gen/v2ray_geosite.pb.cc) {
appdataXml.files += ./assets/qv2ray.metainfo.xml Qv2rayQMakeError("Protobuf headers for v2ray geosite is missing.")
appdataXml.path = $$PREFIX/share/metainfo/
INSTALLS += appdataXml
DEFINES += WITH_FLATHUB_CONFIG_PATH
} }
SOURCES += $$PWD/libs/gen/v2ray_geosite.pb.cc
HEADERS += $$PWD/libs/gen/v2ray_geosite.pb.h
# General header and source files for gRPC and libQvb
message(" ")
use_grpc {
DEFINES += WITH_LIB_GRPCPP
message("Qv2ray will use gRPC as API backend")
!exists($$PWD/libs/gen/v2ray_api.grpc.pb.h) || !exists($$PWD/libs/gen/v2ray_api.grpc.pb.cc) || !exists($$PWD/libs/gen/v2ray_api.pb.h) || !exists($$PWD/libs/gen/v2ray_api.pb.cc) {
Qv2rayQMakeError("gRPC and protobuf headers for v2ray API is missing.")
}
SOURCES += $$PWD/libs/gen/v2ray_api.pb.cc $$PWD/libs/gen/v2ray_api.grpc.pb.cc
HEADERS += $$PWD/libs/gen/v2ray_api.pb.h $$PWD/libs/gen/v2ray_api.grpc.pb.h
} else {
message("Qv2ray will use libqvb as API backend")
!exists($$PWD/libs/libqvb/build/libqvb.h) {
Qv2rayQMakeError("libs/libqvb/build/libqvb.h is missing.")
}
HEADERS += $$PWD/libs/libqvb/build/libqvb.h
}
# Misc steps to build Qv2ray.
include($$PWD/makespec/99-others.pri)
message(" ") message(" ")
message("Done configuring Qv2ray project. Build output will be at:" $$OUT_PWD) message("This Qv2ray build contains: ")
message("Type `make` or `mingw32-make` to start building Qv2ray") message(" --> $${size(SOURCES)} source files")
message(" --> $${size(HEADERS)} header files")
message(" --> $${size(FORMS)} ui files")
message(" --> $${size(TRANSLATIONS)} translation files")
message(" --> $${size(EXTRA_TRANSLATIONS)} extra translation files")
message(" ")
message("Finished configuring Qv2ray project. Build output will be at:" $$OUT_PWD)
message("Type 'make' or 'nmake' to start building Qv2ray")

View File

@ -41,6 +41,7 @@
- Copyright (c) 2019 Itay Grudev (@itay-grudev): **SingleApplication** (MIT) - Copyright (c) 2019 Itay Grudev (@itay-grudev): **SingleApplication** (MIT)
- Copyright (c) 2019 paceholder (@paceholder): **nodeeditor (QNodeEditor modified by lhy0403)** (BSD-3-Clause) - Copyright (c) 2019 paceholder (@paceholder): **nodeeditor (QNodeEditor modified by lhy0403)** (BSD-3-Clause)
- Copyright (c) 2020 Ram Pani (@DuckSoft): **QvRPCBridge** (WTFPL) - Copyright (c) 2020 Ram Pani (@DuckSoft): **QvRPCBridge** (WTFPL)
- Copyright (c) 2019 ShadowSocks (@shadowsocks): **libQtShadowsocks** (LGPLv3)
## Licences ## Licences

View File

@ -1,84 +0,0 @@
***Note: Qv2ray can only be used for learning related technologies such as Qt/c++/linux/CI/automation and use within the scope permitted by law. Any individual or group may not use Qv2ray for any violation of relevant laws and regulations.***
*Any attempt to download or download any branch or distribution of Qv2ray constitutes your agreement that the author of the project will not be liable for any legal liability arising from your breach of the above guidelines.*
# Qv2ray
[![HitCount](http://hits.dwyl.io/lhy0403/Qv2ray.svg)](http://hits.dwyl.io/lhy0403/Qv2ray) ![GitHub Releases](https://img.shields.io/github/downloads/lhy0403/Qv2ray/latest/total?style=flat-square)
The v2ray GUI client using Qt, supports Windows, Linux and macOS
Supports editing connection config, importing any file and `vmess://` shared link.
Special Thanks to [Hv2ray](https://github.com/aliyuchang33/Hv2ray) by [@aliyuchang33](https://github.com/aliyuchang33)
## Relevant Links
- **Brief Feature Introduction: https://lhy0403.github.io/Qv2ray**
- Latest **[Release Distribution](https://github.com/lhy0403/Qv2ray/releases/latest)**
- Crowdin Translation Platform: **[Public Translation Platform](https://crwd.in/qv2ray)**
- ArchLinux - AUR: **[qv2ray](https://aur.archlinux.org/packages/qv2ray/)**
- **[qv2ray-dev-git](https://aur.archlinux.org/packages/qv2ray-dev-git)**: Development version from branch`dev`, maintained by **[@axionl](https://github.com/axionl)**
- **Note: The development branch contains unstable feature update and (even more) unstable bug fixes, it's not recommended to use a development version in production.**
### First time using Qv2ray please refer to ➡ [User Manual](https://lhy0403.github.io/Qv2ray)
## Qv2ray Project Status
### Jenkins CI
- Server Website: [https://jenkins.lhy0403.top/](https://jenkins.lhy0403.top/)
| | [Master](https://github.com/lhy0403/Qv2ray/tree/master) | [Developement](https://github.com/lhy0403/Qv2ray/tree/dev) | [Version v1](https://github.com/lhy0403/Qv2ray/tree/version-v1) | [Version v2](https://github.com/lhy0403/Qv2ray/tree/version-v2) | Testing |
| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| Linux AppImage | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Release/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Release/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Dev/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Dev/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Version1/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Version1/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Version2/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Version2/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Testing/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-AppImage-Testing/) |
| Windows Zip | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Release/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Release/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Dev/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Dev/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Version1/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Version1/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Version2/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Version2/) | [![Build Status](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Testing/badge/icon)](https://jenkins.lhy0403.top/job/Qv2ray-Win32-Testing/) |
### Travis & Appveyor Cross Platform Build Status
*Travis per machine badge provided by: [badge-matrix](https://github.com/exogen/badge-matrix)*
| OS | [master](https://github.com/lhy0403/Qv2ray/tree/master) | [dev](https://github.com/lhy0403/Qv2ray/tree/dev) | [version-v1](https://github.com/lhy0403/Qv2ray/tree/version-v1) | [version-v2](https://github.com/lhy0403/Qv2ray/tree/version-v2) |
| --------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| [Linux](https://travis-ci.com/lhy0403/Qv2ray) | ![Build Status Linux in master](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=linux&label=Linux-master&branch=master) | ![Build Status Linux in dev](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=linux&label=Linux-dev&branch=dev) | ![Build Status Linux in v1.0.0](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=linux&label=Linux-v1&branch=version-v1) | ![Build Status Linux in v2.0.0](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=linux&label=Linux-v2&branch=version-v2) |
| [macOS](https://travis-ci.com/lhy0403/Qv2ray) | ![Build Status macOS in master](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=osx&label=macOS-master&branch=master) | ![Build Status macOS in dev](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=osx&label=macOS-dev&branch=dev) | ![Build Status macOS in v1](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=osx&label=macOS-v1&branch=version-v1) | ![Build Status macOS in v2](http://badges.herokuapp.com/travis.com/lhy0403/Qv2ray?style=flat-square&env=BADGE=osx&label=macOS-v2&branch=version-v2) |
| [Windows](https://ci.appveyor.com/project/lhy0403/qv2ray) | [![Build status](https://ci.appveyor.com/api/projects/status/i1l524ws0hiitpm4/branch/master?svg=true)](https://ci.appveyor.com/project/lhy0403/qv2ray/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/i1l524ws0hiitpm4/branch/dev?svg=true)](https://ci.appveyor.com/project/lhy0403/qv2ray/branch/dev) | [![Build status](https://ci.appveyor.com/api/projects/status/i1l524ws0hiitpm4/branch/version-v1?svg=true)](https://ci.appveyor.com/project/lhy0403/qv2ray/branch/version-v1) | [![Build status](https://ci.appveyor.com/api/projects/status/i1l524ws0hiitpm4/branch/version-v2?svg=true)](https://ci.appveyor.com/project/lhy0403/qv2ray/branch/version-v2) |
## Special Thanks
| NickName (@GithubAccount) | Contributions |
| ------------------------------------------------------------ | ------------------------------ |
| Leroy.H.Y [@lhy0403](https://github.com/lhy0403) | Qv2ray Current Maintainer |
| Hork [@aliyuchang33](https://github.com/aliyuchang33) | Hv2ray Initial Idea and Design |
| SOneWinstone [@SoneWinstone](https://github.com/SoneWinstone) | Qt HTTP Interactive Logics |
| ArielAxionL [@axionl](https://github.com/axionl) | Qv2ray Artworks |
| TheBadGateway [@thebadgateway](https://github.com/thebadgateway) | Translation for Russian |
## Open Source License
Qv2ray is licensed under [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
Submodule [X2Struct](https://github.com/xyz347/x2struct) is licensed under ![License: GPL v3](https://img.shields.io/badge/License-MIT-blue.svg)
```
Qv2ray, A Qt frontend for v2ray. Written in c++
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
```

View File

@ -1 +0,0 @@
theme: jekyll-theme-architect

View File

@ -4,7 +4,7 @@ p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'WenQuanYi Micro Hei'; font-size:10pt; font-weight:400; font-style:normal;"> </style></head><body style=" font-family:'WenQuanYi Micro Hei'; font-size:10pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; font-weight:600; color:#d68952;">This program comes with ABSOLUTELY NO WARRANTY.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; font-weight:600; color:#d68952;">This program comes with ABSOLUTELY NO WARRANTY.</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; font-weight:600; color:#d68952;">This is free software, and you are welcome to redistribute it under certain conditions.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; font-weight:600; color:#d68952;">This is free software, and you are welcome to redistribute it under certain conditions.</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2019 2020 Qv2ray Developemnt Group.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2019-2020 Qv2ray Developemnt Group.</span></p>
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Noto Sans'; color:#d68952;"><br /></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Noto Sans'; color:#d68952;"><br /></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; font-weight:600; color:#d68952;">Libraries that have been used in Qv2ray are listed below (Sorted by date added):</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; font-weight:600; color:#d68952;">Libraries that have been used in Qv2ray are listed below (Sorted by date added):</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 dridk (@dridk): X2Struct (Apache)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 dridk (@dridk): X2Struct (Apache)</span></p>
@ -15,4 +15,5 @@ p, li { white-space: pre-wrap; }
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 Itay Grudev (@itay-grudev): SingleApplication (MIT)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 Itay Grudev (@itay-grudev): SingleApplication (MIT)</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 paceholder (@paceholder): nodeeditor (QNodeEditor modified by lhy0403) (BSD-3-Clause)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 paceholder (@paceholder): nodeeditor (QNodeEditor modified by lhy0403) (BSD-3-Clause)</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2019 TheWanderingCoel (@TheWanderingCoel): ShadowClash (launchatlogin) (GPLv3)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2019 TheWanderingCoel (@TheWanderingCoel): ShadowClash (launchatlogin) (GPLv3)</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 DuckSoft (@DuckSoft): QvRPCBridge (WTFPL)</span></p></body></html> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2020 DuckSoft (@DuckSoft): QvRPCBridge (WTFPL)</span></p>
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MonoSpace'; color:#d68952;">Copyright (c) 2019 ShadowSocks (@shadowsocks): libQtShadowsocks (LGPLv3)</span></p></body></html>

View File

@ -29,11 +29,14 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.0.0" date="2019-1-24"> <release version="2.0.1" date="2019-1-28">
<description> <description>
<ul> <ul>
<li>fix: fixed windows startup issue</li> <li>Prevent messing up vcore logs</li>
<li>fix: autostart, tProxyPrep and negative API fixed</li> <li>upstream: solved QTimer issue and golang memory leak issue</li>
<li>fix: fixed duplicated scheme:// in GNOME system settings</li>
<li>add: added os process signals support</li>
<li>add: enable hiding Qv2ray when performing screenshot, fixed #283</li>
</ul> </ul>
</description> </description>
</release> </release>

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

Binary file not shown.

Binary file not shown.

@ -1 +1 @@
Subproject commit c7199c1d876c012f8b40a38de25c90f69dcda0ed Subproject commit e4765aee61f58c13a8a5228727856ae622ab4e3c

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,32 @@
Qv2ray Dependencies Directory for Windows x64
THIS IS A PLACEHOLDER WHICH ALLOWS GIT TO INCLUDE THIS DIRECTORY
If you want to build Qv2ray on Windows, please download the gRPC and
protobuf headers and libraries according to your CPU architecture:
- Go to https://github.com/Qv2ray/Qv2ray-deps/releases
- Download "Qv2ray-deps-x64-windows.7z"
Then extract the 7z zipball here.
Make sure these file exists:
- Qv2raySourceRoot/libs/x64-windows/tools/grpc/grpc_cpp_plugin.exe
- Qv2raySourceRoot/libs/x64-windows/tools/protobuf/protoc.exe
# ======================================================================
64 位 Windows 的 Qv2ray 依赖文件夹
这个文件的存在使得 git 可以添加此文件夹
如果你想在 Windows 下编译 Qv2ray 请遵循这些步骤下载 gRPC 和 protobuf 头文件
和库文件:
- 访问 https://github.com/Qv2ray/Qv2ray-deps/releases
- 下载 "Qv2ray-deps-x64-windows.7z"
然后将这个 7z 压缩包解压到这里
请确保这些文件存在:
- Qv2ray源码根目录/libs/x64-windows/tools/grpc/grpc_cpp_plugin.exe
- Qv2ray源码根目录/libs/x64-windows/tools/protobuf/protoc.exe

View File

@ -0,0 +1,32 @@
Qv2ray Dependencies Directory for Windows x86
THIS IS A PLACEHOLDER WHICH ALLOWS GIT TO INCLUDE THIS DIRECTORY
If you want to build Qv2ray on Windows, please download the gRPC and
protobuf headers and libraries according to your CPU architecture:
- Go to https://github.com/Qv2ray/Qv2ray-deps/releases
- Download "Qv2ray-deps-x86-windows.7z"
Then extract the 7z zipball here.
Make sure these file exists:
- Qv2raySourceRoot/libs/x86-windows/tools/grpc/grpc_cpp_plugin.exe
- Qv2raySourceRoot/libs/x86-windows/tools/protobuf/protoc.exe
# ======================================================================
32 位 Windows 的 Qv2ray 依赖文件夹
这个文件的存在使得 git 可以添加此文件夹
如果你想在 Windows 下编译 Qv2ray 请遵循这些步骤下载 gRPC 和 protobuf 头文件
和库文件:
- 访问 https://github.com/Qv2ray/Qv2ray-deps/releases
- 下载 "Qv2ray-deps-x86-windows.7z"
然后将这个 7z 压缩包解压到这里
请确保这些文件存在:
- Qv2ray源码根目录/libs/x86-windows/tools/grpc/grpc_cpp_plugin.exe
- Qv2ray源码根目录/libs/x86-windows/tools/protobuf/protoc.exe

48
makespec/00-deps.pri Normal file
View File

@ -0,0 +1,48 @@
message(" ")
message("Configuring Qv2ray Dependencies...")
defineTest(Qv2rayQMakeError) {
message(" ")
message("-----------------------------------------------")
message("Cannot continue: ")
message(" --> Qv2ray is not properly configured yet: ")
message(" $$ARGS")
message(" --> Please read the build wiki: https://github.com/Qv2ray/Qv2ray/wiki/Manually-Build-Qv2ray")
message("-----------------------------------------------")
message(" ")
warning("IF YOU THINK IT'S A MISTAKE, PLEASE OPEN AN ISSUE")
error("! ABORTING THE BUILD !")
message(" ")
}
CONFIG += enable_decoder_qr_code enable_encoder_qr_code
include($$PWD/../3rdparty/qzxing/src/QZXing-components.pri)
include($$PWD/../3rdparty/SingleApplication/singleapplication.pri)
include($$PWD/../3rdparty/QNodeEditor/QNodeEditor.pri)
#include(3rdparty/x2struct/x2struct.pri)
message("Adding QHttpServer Support")
message(" --> Adding qhttpserver")
HEADERS += \
$$PWD/../3rdparty/qhttpserver/src/qhttpconnection.h \
$$PWD/../3rdparty/qhttpserver/src/qhttprequest.h \
$$PWD/../3rdparty/qhttpserver/src/qhttpresponse.h \
$$PWD/../3rdparty/qhttpserver/src/qhttpserver.h \
$$PWD/../3rdparty/qhttpserver/src/qhttpserverapi.h \
$$PWD/../3rdparty/qhttpserver/src/qhttpserverfwd.h
SOURCES += \
$$PWD/../3rdparty/qhttpserver/src/qhttpconnection.cpp \
$$PWD/../3rdparty/qhttpserver/src/qhttprequest.cpp \
$$PWD/../3rdparty/qhttpserver/src/qhttpresponse.cpp \
$$PWD/../3rdparty/qhttpserver/src/qhttpserver.cpp
INCLUDEPATH += 3rdparty/qhttpserver/src/
message(" --> Adding http parser")
HEADERS += $$PWD/../3rdparty/qhttpserver/http-parser/http_parser.h
SOURCES += $$PWD/../3rdparty/qhttpserver/http-parser/http_parser.c
INCLUDEPATH += $$PWD/../3rdparty/qhttpserver/http-parser/

View File

@ -0,0 +1,50 @@
message(" ")
defineTest(Qv2rayAddFile) {
ext = $$take_last(ARGS)
filename = $${take_first(ARGS)}.$${ext}
qmake_debug: message("Qv2rayAddFile: filename: $$filename")
!exists($$filename) {
error("File: \"$$filename\" is not found, Qv2ray build preparation cannot continue")
}
equals(ext, "cpp") {
SOURCES += $$filename
} else {
equals(ext, "hpp") {
HEADERS += $$filename
} else {
equals(ext, "ui") {
FORMS += $$filename
} else {
error("Unknown extension: $${ext}")
}
}
}
export(SOURCES)
export(HEADERS)
export(FORMS)
}
defineTest(Qv2rayAddSource) {
# Module Compnent Filename extlist
module = $$take_first(ARGS)
component = $$take_first(ARGS)
filename = $$take_first(ARGS)
extlist = $$ARGS
FILEPATH = "$$PWD/src/$${module}"
qmake_debug: message(Qv2rayAddSource: Adding \"$${filename}\" of module \"$${module}\", component \"$${component}\" to the project)
equals(component, "_") {
qmake_debug: message("Qv2rayAddSource: Component is empty, ignore")
FILEPATH += "/$${filename}"
FILEPATH=$$join(FILEPATH)
} else {
FILEPATH += "/$${component}/$${filename}"
FILEPATH=$$join(FILEPATH)
}
qmake_debug: message("Qv2rayAddSource: filepath: $${FILEPATH}, extlist: $${extlist}")
for(iterate, extlist) {
Qv2rayAddFile($$FILEPATH, $$iterate)
}
export(SOURCES)
export(HEADERS)
export(FORMS)
}

View File

@ -0,0 +1,15 @@
message(" ")
message("Looking for language support.")
QM_FILES_RESOURCE_PREFIX = "translations"
for(var, $$list($$files("$$PWD/../translations/*.ts", true))) {
LOCALE_FILENAME = $$basename(var)
message(" --> Found:" $$LOCALE_FILENAME)
!equals(LOCALE_FILENAME, "en_US.ts") {
# ONLY USED IN LRELEASE CONTEXT
# en_US is not EXTRA...
EXTRA_TRANSLATIONS += $$PWD/../translations/$$LOCALE_FILENAME
}
}
TRANSLATIONS += $$PWD/../translations/en_US.ts
message("Qv2ray will build with" $${replace(EXTRA_TRANSLATIONS, "$$PWD/../translations/", "")} and $${replace(TRANSLATIONS, "$$PWD/../translations/", "")})

43
makespec/03-Windows.pri Normal file
View File

@ -0,0 +1,43 @@
message(" ")
message("Configuring for Windows environment")
QMAKE_CXXFLAGS += /MP /wo4251
DEFINES += QHTTPSERVER_EXPORT
CONFIG += Qv2ray_Windows use_grpc
!contains(QMAKE_TARGET.arch, x86_64) {
CONFIG+=Qv2ray_win32
GRPC_DEPS_PATH=$$PWD/../libs/x86-windows
#
!no_generate_headers: message(" --> Generating gRPC and protobuf headers for Windows x86")
!no_generate_headers: system("$$PWD/../tools/win32-generate-pb.bat")
} else {
CONFIG+=Qv2ray_win64
GRPC_DEPS_PATH=$$PWD/../libs/x64-windows
#
!no_generate_headers: message(" --> Generating gRPC and protobuf headers for Windows x64")
!no_generate_headers: system("$$PWD/../tools/win64-generate-pb.bat")
}
message(" --> Applying a hack for protobuf header")
DEFINES += _WIN32_WINNT=0x600
DEPENDPATH += $$GRPC_DEPS_PATH/include
INCLUDEPATH += $$GRPC_DEPS_PATH/include
message(" --> Setting up target descriptions")
QMAKE_TARGET_DESCRIPTION = "Qv2ray, a cross-platform v2ray GUI client."
QMAKE_TARGET_PRODUCT = "Qv2ray"
Qv2ray_debug: GRPC_LIB_PATH=$$GRPC_DEPS_PATH/debug
Qv2ray_release: GRPC_LIB_PATH=$$GRPC_DEPS_PATH
message(" --> WIN32: Linking against gRPC library: $$GRPC_LIB_PATH")
Qv2ray_debug: LIBS += -L$$GRPC_LIB_PATH/lib/ -laddress_sorting -lcares -lgrpc++_unsecure -lupb -lzlibd -lgrpc_unsecure -lgpr
Qv2ray_release: LIBS += -L$$GRPC_LIB_PATH/lib/ -laddress_sorting -lcares -lgrpc++_unsecure -lupb -lzlib -lgrpc_unsecure -lgpr
message(" --> WIN32: Linking against protobuf library: $$GRPC_LIB_PATH")
Qv2ray_release: LIBS += -lmsvcrt -L$$GRPC_LIB_PATH/lib/ -llibprotobuf
Qv2ray_debug: LIBS += -lmsvcrtd -L$$GRPC_LIB_PATH/lib/ -llibprotobufd
message(" --> Linking against winHTTP and winSock2.")
LIBS += -lwinhttp -lwininet -lws2_32 -luser32

28
makespec/03-unix.pri Normal file
View File

@ -0,0 +1,28 @@
message(" ")
win32: Qv2rayQMakeError("Do not include this file in Windows platform.")
# For Linux and macOS
message("Configuring for unix-like environment")
message(" --> Setting up QMAKE_CXXFLAGS")
QMAKE_CXXFLAGS += -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-variable
# macOS homebrew include path
message(" --> Adding local include folder to search path")
INCLUDEPATH += /usr/local/include/
# For protobuf in linux and macOS
message(" --> Linking against protobuf library.")
LIBS += -L/usr/local/lib -lprotobuf
message(" --> Generating geosite headers for Unix")
system("$$PWD/../tools/unix-generate-geosite.sh $$PWD")
use_grpc {
no_generate_headers {
message(" --> Skipped generation of protobuf and/or gRPC header files")
} else {
message(" --> Generating gRPC and protobuf headers for Unix")
system("$$PWD/../tools/unix-generate-api.sh $$PWD")
}
}

View File

@ -0,0 +1,26 @@
message(" ")
message("Configuring Qv2ray build for Linux")
isEmpty(PREFIX) {
PREFIX=/usr/local
}
use_grpc {
# For gRPC and protobuf in linux
message(" --> Linking against gRPC and protobuf library.")
LIBS += -L/usr/local/lib -lgrpc++ -lgrpc
} else {
message(" --> Linking libqvb static library, for Linux platform.")
LIBS += -L$$PWD/../libs/ -lqvb-linux64
}
message(" --> Generating desktop dependency.")
desktop.files += $$PWD/../assets/qv2ray.desktop
desktop.path = $$PREFIX/share/applications/
message(" --> Generating icons dependency.")
icon.files += $$PWD/../assets/icons/qv2ray.png
icon.path = $$PREFIX/share/icons/hicolor/256x256/apps/
target.path = $$PREFIX/bin/
INSTALLS += target desktop icon

View File

@ -0,0 +1,11 @@
message(" ")
# For Linux and macOS
message("Configuring for macOS specific environment")
LIBS += -framework Carbon -framework Cocoa
use_grpc: error("The use of gRPC backend is not supported on macOS platform.")
message(" --> Linking libqvb static library and Security framework, for macOS platform.")
LIBS += -L$$PWD/../libs/ -lqvb-darwin
LIBS += -framework Security

48
makespec/99-others.pri Normal file
View File

@ -0,0 +1,48 @@
message(" ")
with_metainfo {
message(" --> Generating metainfo dependency.")
appdataXml.files += ./assets/qv2ray.metainfo.xml
appdataXml.path = $$PREFIX/share/metainfo/
INSTALLS += appdataXml
DEFINES += WITH_FLATHUB_CONFIG_PATH
}
# Automatically increase build number.
no_increase_build_number | qmake_lupdate {
message("QV2RAY_BUILD_VERSION will not be increased")
} else {
QV2RAY_BUILD_VERSION = $$num_add($$QV2RAY_BUILD_VERSION, 1)
write_file($$PWD/BUILDVERSION, QV2RAY_BUILD_VERSION)
}
qmake_lupdate {
message(" ")
message("Running lupdate...")
message(" ")
lupdate_output = $$system(lupdate $$SOURCES $$HEADERS $$FORMS -ts $$PWD/$$TRANSLATIONS -no-ui-lines)
message(" $$lupdate_output")
message("lupdate finished.")
}
# Qv2ray manual build info
!no_build_info {
_QV2RAY_BUILD_INFO_STR_=$$getenv(_QV2RAY_BUILD_INFO_)
_QV2RAY_BUILD_EXTRA_INFO_STR_=$$getenv(_QV2RAY_BUILD_EXTRA_INFO_)
isEmpty(_QV2RAY_BUILD_INFO_STR_) {
_QV2RAY_BUILD_INFO_STR_ = "Qv2ray from manual build"
}
isEmpty(_QV2RAY_BUILD_EXTRA_INFO_STR_) {
_QV2RAY_BUILD_EXTRA_INFO_STR_ = "Qv2ray $$VERSION"
}
} else {
_QV2RAY_BUILD_INFO_STR_ = "No Info"
_QV2RAY_BUILD_EXTRA_INFO_STR_ = "No Extra Info"
}
message("Qv2ray build info:")
message(" --> $$_QV2RAY_BUILD_INFO_STR_")
message(" --> $$_QV2RAY_BUILD_EXTRA_INFO_STR_")
DEFINES += _QV2RAY_BUILD_INFO_STR_=\"\\\"$${_QV2RAY_BUILD_INFO_STR_}\\\"\" _QV2RAY_BUILD_EXTRA_INFO_STR_=\"\\\"$${_QV2RAY_BUILD_EXTRA_INFO_STR_}\\\"\"

1
makespec/BUILDVERSION Normal file
View File

@ -0,0 +1 @@
3856

1
makespec/VERSION Normal file
View File

@ -0,0 +1 @@
2.0.1

121
snap/snapcraft.yaml Normal file
View File

@ -0,0 +1,121 @@
name: qv2ray
base: core18
adopt-info: qv2ray
icon: assets/icons/qv2ray.png
license: GPL-3.0
grade: devel
confinement: devmode
plugs:
gsettings:
gtk-3-themes:
interface: content
target: $SNAP/data-dir/themes
default-provider: gtk-common-themes
icon-themes:
interface: content
target: $SNAP/data-dir/icons
default-provider: gtk-common-themes
sound-themes:
interface: content
target: $SNAP/data-dir/sounds
default-provider: gtk-common-themes
apps:
qv2ray:
command: bin/desktop-launch qv2ray
environment:
QT_QPA_PLATFORMTHEME: gtk3
common-id: com.github.Qv2ray
desktop: "usr/share/applications/qv2ray.desktop"
parts:
qv2ray:
plugin: qmake
source-type: git
source: .
parse-info: [usr/share/metainfo/qv2ray.metainfo.xml]
build-packages:
- build-essential
- qttools5-dev-tools
- qt5-default
- libgrpc++-dev
- libprotobuf-dev
- protobuf-compiler-grpc
stage-packages:
- libgcc1
- libstdc++6
- libssl1.1
- libgrpc++1
- libprotobuf17
- libqt5core5a
- libqt5gui5
- libqt5network5
- libqt5widgets5
- fonts-noto-cjk
- libglib2.0-bin
options:
- PREFIX=/usr
- CONFIG+=with_metainfo
- CONFIG+=use_grpc
override-pull: |
snapcraftctl pull
build_number=$(cat makespec/BUILDVERSION)
tag=$(git describe --abbrev=0 --tags)
version=$tag.$build_number
snapcraftctl set-version "$version"
sed -i 's|^Icon=.*|Icon=/usr/share/icons/hicolor/256x256/apps/qv2ray.png|g' assets/qv2ray.desktop
after:
- mytools
- desktop-qt5
desktop-qt5:
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
source-subdir: qt
plugin: make
make-parameters: ["FLAVOR=qt5"]
build-packages:
- build-essential
- qtbase5-dev
- dpkg-dev
stage-packages:
- libxkbcommon0
- ttf-ubuntu-font-family
- dmz-cursor-theme
- light-themes
- adwaita-icon-theme
- gnome-themes-standard
- shared-mime-info
- libqt5gui5
- libgdk-pixbuf2.0-0
- libqt5svg5 # for loading icon themes which are svg
- try: [appmenu-qt5] # not available on core18
- locales-all
- xdg-user-dirs
- fcitx-frontend-qt5
after:
- mytools
qt5-gtk-platform:
plugin: nil
stage-packages:
- qt5-gtk-platformtheme
after:
- mytools
mytools:
plugin: nil
build-packages:
- dirmngr
override-pull: |
echo 'deb http://archive.neon.kde.org/unstable bionic main' > /etc/apt/sources.list.d/neon.list
echo "deb http://ppa.launchpad.net/ymshenyu/grpc/ubuntu bionic main" >> /etc/apt/sources.list.d/tools.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E6D4736255751E5D
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 281f24e574404629aa3bda1a4f10c386c55cdb04
sudo apt-get update
snapcraftctl pull
override-build: |
snapcraftctl build
sudo apt upgrade -y
sudo apt dist-upgrade -y

View File

@ -1,4 +0,0 @@
#pragma once
// Qv2ray build features.
//#ifdef __Q_Build_Without_Chart

View File

@ -1,407 +0,0 @@
#ifndef V2CONFIG_H
#define V2CONFIG_H
#include <list>
#include <string>
#include <QJsonObject>
#include <QJsonArray>
#include <QVariant>
#include <x2struct/x2struct.hpp>
using namespace x2struct;
using namespace std;
#define SAFE_TYPEDEF(Base, name) \
class name : public Base { \
public: \
template <class... Args> \
explicit name (Args... args) : Base(args...) {} \
const Base& raw() const { return *this; } \
};
struct QvConfigIdentifier {
QString subscriptionName;
QString connectionName;
QvConfigIdentifier() { };
//
bool isEmpty()
{
return connectionName.isEmpty();
}
QvConfigIdentifier(QString connectionName)
{
this->connectionName = connectionName;
}
QvConfigIdentifier(QString connectionName, QString subscriptionName)
{
this->connectionName = connectionName;
this->subscriptionName = subscriptionName;
}
const QString IdentifierString() const
{
return connectionName + (subscriptionName.isEmpty() ? "" : " (" + subscriptionName + ")");
}
friend bool operator==(QvConfigIdentifier &left, QvConfigIdentifier &right)
{
return left.subscriptionName == right.subscriptionName && left.connectionName == right.connectionName;
}
friend bool operator!=(QvConfigIdentifier &left, QvConfigIdentifier &right)
{
return !(left == right);
}
friend bool operator==(QvConfigIdentifier &left, QString &right)
{
return left.IdentifierString() == right;
}
friend bool operator!=(QvConfigIdentifier &left, QString &right)
{
return !(left.IdentifierString() == right);
}
// To make QMap happy
friend bool operator<(const QvConfigIdentifier left, const QvConfigIdentifier right)
{
return left.IdentifierString() < right.IdentifierString();
}
friend bool operator>(const QvConfigIdentifier left, const QvConfigIdentifier right)
{
return left.IdentifierString() > right.IdentifierString();
}
XTOSTRUCT(O(subscriptionName, connectionName))
};
Q_DECLARE_METATYPE(QvConfigIdentifier);
namespace Qv2ray
{
// To prevent anonying QJsonObject misuse
SAFE_TYPEDEF(QJsonObject, INBOUNDSETTING)
SAFE_TYPEDEF(QJsonObject, OUTBOUNDSETTING)
SAFE_TYPEDEF(QJsonObject, INBOUND)
SAFE_TYPEDEF(QJsonObject, OUTBOUND)
SAFE_TYPEDEF(QJsonObject, CONFIGROOT)
SAFE_TYPEDEF(QJsonObject, PROXYSETTING)
//
SAFE_TYPEDEF(QJsonArray, INOUTLIST)
SAFE_TYPEDEF(INOUTLIST, OUTBOUNDS)
SAFE_TYPEDEF(INOUTLIST, INBOUNDS)
SAFE_TYPEDEF(QJsonObject, ROUTING)
SAFE_TYPEDEF(QJsonObject, ROUTERULE)
SAFE_TYPEDEF(QJsonArray, ROUTERULELIST)
}
/* ----------------------------------------- */
namespace Qv2ray
{
namespace V2ConfigModels
{
//
// Used in config generation
struct AccountObject {
QString user;
QString pass;
XTOSTRUCT(O(user, pass))
};
//
//
struct ApiObject {
QString tag;
QList<QString> services;
ApiObject() : tag("api"), services() {}
XTOSTRUCT(O(tag, services))
};
//
//
struct SystemPolicyObject {
bool statsInboundUplink;
bool statsInboundDownlink;
SystemPolicyObject() : statsInboundUplink(), statsInboundDownlink() {}
XTOSTRUCT(O(statsInboundUplink, statsInboundDownlink))
};
//
//
struct LevelPolicyObject {
int handshake;
int connIdle;
int uplinkOnly;
int downlinkOnly;
bool statsUserUplink;
bool statsUserDownlink;
int bufferSize;
LevelPolicyObject(): handshake(), connIdle(), uplinkOnly(), downlinkOnly(), statsUserUplink(), statsUserDownlink(), bufferSize() {}
XTOSTRUCT(O(handshake, connIdle, uplinkOnly, downlinkOnly, statsUserUplink, statsUserDownlink, bufferSize))
};
//
//
struct PolicyObject {
QMap<QString, LevelPolicyObject> level;
list<SystemPolicyObject> system;
PolicyObject(): level(), system() {}
XTOSTRUCT(O(level, system))
};
//
//
struct RuleObject {
// Added due to the request of @aliyuchang33
bool QV2RAY_RULE_ENABLED;
bool QV2RAY_RULE_USE_BALANCER;
QString QV2RAY_RULE_TAG;
//
QString type;
QList<QString> domain;
QList<QString> ip;
QString port;
QString network;
QList<QString> source;
QList<QString> user;
QList<QString> inboundTag;
QList<QString> protocol;
QString attrs;
QString outboundTag;
QString balancerTag;
RuleObject() : QV2RAY_RULE_ENABLED(true), QV2RAY_RULE_USE_BALANCER(false), QV2RAY_RULE_TAG("new rule"), type("field"), domain(), ip(), port("1-65535"), network(""), source(), user(), inboundTag(), protocol(), attrs(), outboundTag(""), balancerTag("") {}
XTOSTRUCT(O(QV2RAY_RULE_ENABLED, QV2RAY_RULE_USE_BALANCER, QV2RAY_RULE_TAG, type, domain, ip, port, network, source, user, inboundTag, protocol, attrs, outboundTag, balancerTag))
};
//
//
struct BalancerObject {
QString tag ;
QList<QString> selector;
BalancerObject() : tag(), selector() {}
XTOSTRUCT(O(tag, selector))
};
//
//
namespace TSObjects
{
struct HTTPRequestObject {
QString version;
QString method;
QList<QString> path;
QMap<QString, QList<QString>> headers;
HTTPRequestObject(): version("1.1"), method("GET"), path(), headers() {}
XTOSTRUCT(O(version, method, path, headers))
};
//
//
struct HTTPResponseObject {
QString version;
QString status;
QString reason;
QMap<QString, QList<QString>> headers;
HTTPResponseObject(): version("1.1"), status("200"), reason("OK"), headers() {}
XTOSTRUCT(O(version, status, reason, headers))
};
//
//
struct TCPHeader_M_Object {
QString type;
HTTPRequestObject request;
HTTPResponseObject response;
TCPHeader_M_Object(): type("none"), request(), response() {}
XTOSTRUCT(O(type, request, response))
};
//
//
struct HeaderObject {
QString type;
HeaderObject(): type("none") {}
XTOSTRUCT(O(type))
};
//
//
struct TCPObject {
TCPHeader_M_Object header;
TCPObject(): header() {}
XTOSTRUCT(O(header))
};
//
//
struct KCPObject {
int mtu = 1350;
int tti = 20;
int uplinkCapacity = 5;
int downlinkCapacity = 20;
bool congestion = false;
int readBufferSize = 1;
int writeBufferSize = 1;
HeaderObject header;
KCPObject(): header() {}
XTOSTRUCT(O(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header))
};
//
//
struct WebSocketObject {
QString path;
QMap<QString, QString> headers;
WebSocketObject(): path("/"), headers() {}
XTOSTRUCT(O(path, headers))
};
//
//
struct HttpObject {
QList<QString> host;
QString path;
HttpObject() : host(), path("/") {}
XTOSTRUCT(O(host, path))
};
//
//
struct DomainSocketObject {
QString path;
DomainSocketObject(): path("/") {}
XTOSTRUCT(O(path))
};
//
//
struct QuicObject {
QString security;
QString key;
HeaderObject header;
QuicObject(): security(""), key(""), header() {}
XTOSTRUCT(O(security, key, header))
};
//
//
struct SockoptObject {
int mark;
bool tcpFastOpen;
QString tproxy;
SockoptObject(): mark(0), tcpFastOpen(false), tproxy("off") {}
XTOSTRUCT(O(mark, tcpFastOpen, tproxy))
};
//
//
struct CertificateObject {
QString usage;
QString certificateFile;
QString keyFile;
QList<QString> certificate;
QList<QString> key;
CertificateObject(): usage(), certificateFile(), keyFile(), certificate(), key() {}
XTOSTRUCT(O(usage, certificateFile, keyFile, certificate, key))
};
//
//
struct TLSObject {
QString serverName;
bool allowInsecure;
QList<QString> alpn;
list<CertificateObject> certificates;
bool disableSystemRoot;
TLSObject(): serverName(), allowInsecure(), certificates(), disableSystemRoot() {}
XTOSTRUCT(O(serverName, allowInsecure, alpn, certificates, disableSystemRoot))
};
}
//
//
struct SniffingObject {
bool enabled = false;
QList<QString> destOverride;
SniffingObject(): enabled(), destOverride() {}
XTOSTRUCT(O(enabled, destOverride))
};
//
//
struct StreamSettingsObject {
QString network;
QString security;
TSObjects::SockoptObject sockopt;
TSObjects::TLSObject tlsSettings;
TSObjects::TCPObject tcpSettings;
TSObjects::KCPObject kcpSettings;
TSObjects::WebSocketObject wsSettings;
TSObjects::HttpObject httpSettings;
TSObjects::DomainSocketObject dsSettings;
TSObjects::QuicObject quicSettings;
StreamSettingsObject(): network("tcp"), security("none"), sockopt(), tlsSettings(), tcpSettings(), kcpSettings(), wsSettings(), httpSettings(), dsSettings(), quicSettings() {}
XTOSTRUCT(O(network, security, sockopt, tcpSettings, tlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings))
};
//
//
struct MuxObject {
bool enabled;
int concurrency;
MuxObject(): enabled(), concurrency() {}
XTOSTRUCT(O(enabled, concurrency))
};
//
// Some protocols from: https://v2ray.com/chapter_02/02_protocols.html
namespace Protocols
{
// DNS, OutBound
struct DNSOut {
QString network;
QString address;
int port;
DNSOut(): network(""), address("0.0.0.0"), port(0) {}
XTOSTRUCT(O(network, address, port))
};
//
// MTProto, InBound || OutBound
struct MTProtoIn {
struct UserObject {
QString email;
int level;
QString secret;
UserObject() : email("user@domain.com"), level(0), secret("") {}
XTOSTRUCT(O(email, level, secret))
};
list<UserObject> users;
XTOSTRUCT(O(users))
};
//
// Socks, OutBound
struct SocksServerObject {
struct UserObject {
QString user;
QString pass;
int level;
UserObject(): user("username"), pass("password"), level(0) {}
XTOSTRUCT(O(user, pass, level))
};
QString address;
int port;
list<UserObject> users;
SocksServerObject(): address("0.0.0.0"), port(0), users() {}
XTOSTRUCT(O(address, port, users))
};
//
// VMess Server
struct VMessServerObject {
struct UserObject {
QString id;
int alterId;
QString security;
int level;
UserObject() : id(""), alterId(64), security("auto"), level(0) {}
XTOSTRUCT(O(id, alterId, security, level))
};
QString address;
int port;
list<UserObject> users;
VMessServerObject(): address(""), port(0), users() {}
XTOSTRUCT(O(address, port, users))
};
//
// ShadowSocks Server
struct ShadowSocksServerObject {
QString email;
QString address;
QString method;
QString password;
bool ota;
int level;
int port;
ShadowSocksServerObject(): email("user@domain.com"), address("0.0.0.0"), method("aes-256-cfb"), password(""), ota(false), level(0), port(0) {}
XTOSTRUCT(O(email, address, port, method, password, ota, level))
};
}
}
}
using namespace Qv2ray;
using namespace Qv2ray::V2ConfigModels;
using namespace Qv2ray::V2ConfigModels::Protocols;
#endif

View File

@ -1,74 +0,0 @@
#include "QvCoreConfigOperations.hpp"
namespace Qv2ray
{
namespace ConfigOperations
{
CONFIGROOT _ReadConnection(const QString &connection)
{
QString jsonString = StringFromFile(new QFile(connection));
auto conf = CONFIGROOT(JsonFromString(jsonString));
if (conf.count() == 0) {
LOG(MODULE_CONFIG, "WARN: Possible file corruption, failed to load file: " + connection + " --> File might be empty.")
}
return conf;
}
QMap<QString, CONFIGROOT> GetRegularConnections(QStringList connectionNames)
{
QMap<QString, CONFIGROOT> list;
for (auto conn : connectionNames) {
list.insert(conn, _ReadConnection(QV2RAY_CONFIG_DIR + conn + QV2RAY_CONFIG_FILE_EXTENSION));
}
return list;
}
QMap<QString, CONFIGROOT> GetSubscriptionConnection(QString subscription)
{
auto _files = GetFileList(QV2RAY_SUBSCRIPTION_DIR + subscription);
QMap<QString, CONFIGROOT> _config;
for (auto _file : _files) {
// check if is proper connection file.
if (_file.endsWith(QV2RAY_CONFIG_FILE_EXTENSION)) {
auto confName = _file;
// Remove the extension
confName.chop(sizeof(QV2RAY_CONFIG_FILE_EXTENSION) - 1);
_config[confName] = _ReadConnection(QV2RAY_SUBSCRIPTION_DIR + subscription + "/" + _file);
} else {
LOG(MODULE_SUBSCRIPTION, "Found a file in subscription folder but without proper suffix: " + _file)
}
}
if (_config.isEmpty()) {
LOG(MODULE_SUBSCRIPTION, "WARN: Maybe loading an empty subscrption: " + subscription)
}
return _config;
}
QMap<QString, QMap<QString, CONFIGROOT>> GetSubscriptionConnections(QStringList subscriptions)
{
// SUB-NAME CONN-NAME CONN-ROOT
QMap<QString, QMap<QString, CONFIGROOT>> list;
for (auto singleSub : subscriptions) {
LOG(MODULE_SUBSCRIPTION, "Processing subscription: " + singleSub)
list[singleSub] = GetSubscriptionConnection(singleSub);
}
return list;
}
bool CheckIsComplexConfig(CONFIGROOT root)
{
bool cRouting = root.contains("routing");
bool cRule = cRouting && root["routing"].toObject().contains("rules");
bool cRules = cRule && root["routing"].toObject()["rules"].toArray().count() > 0;
return cRules;
}
}
}

View File

@ -1,80 +0,0 @@
#ifndef CONFIGGENERATION_H
#define CONFIGGENERATION_H
#include "QvUtils.hpp"
#include "QJsonObjectInsertMacros.h"
#define OUTBOUND_TAG_DIRECT "outBound_DIRECT"
#define OUTBOUND_TAG_PROXY "outBound_PROXY"
#define OUTBOUND_TAG_FORWARD_PROXY "_QV2RAY_FORWARD_PROXY_"
#define API_TAG_DEFAULT "_QV2RAY_API_"
#define API_TAG_INBOUND "_QV2RAY_API_INBOUND_"
#define QV2RAY_USE_FPROXY_KEY "_QV2RAY_USE_GLOBAL_FORWARD_PROXY_"
#define JSON_ROOT_TRY_REMOVE(obj) if (root.contains(obj)) { root.remove(obj); }
namespace Qv2ray
{
// -------------------------- BEGIN GENERAL FUNCTIONS ----------------------------------------------
namespace ConfigOperations
{
QMap<QString, CONFIGROOT> GetRegularConnections(QStringList connections);
QMap<QString, CONFIGROOT> GetSubscriptionConnection(QString subscription);
QMap<QString, QMap<QString, CONFIGROOT>> GetSubscriptionConnections(QStringList subscriptions);
bool CheckIsComplexConfig(CONFIGROOT root);
//
// -------------------------- BEGIN CONFIG CONVERSIONS --------------------------
inline namespace Convertion
{
//int VerifyVMessProtocolString(QString vmess);
QString DecodeSubscriptionString(QByteArray arr);
//
// Save Connection Config
bool SaveConnectionConfig(CONFIGROOT obj, QString *alias, bool canOverrideExisting);
bool SaveSubscriptionConfig(CONFIGROOT obj, const QString &subscription, QString *name);
bool RemoveConnection(const QString &alias);
bool RemoveSubscriptionConnection(const QString &subsName, const QString &name);
bool RenameConnection(const QString &originalName, const QString &newName);
bool RenameSubscription(const QString &originalName, const QString &newName);
// VMess URI Protocol
CONFIGROOT ConvertConfigFromVMessString(const QString &vmess, QString *alias, QString *errMessage);
CONFIGROOT ConvertConfigFromFile(QString sourceFilePath, bool keepInbounds);
QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias);
}
//
// -------------------------- BEGIN CONFIG GENERATIONS ---------------------------------------------
inline namespace Generation
{
ROUTING GenerateRoutes(bool enableProxy, bool cnProxy);
ROUTERULE GenerateSingleRouteRule(QStringList list, bool isDomain, QString outboundTag, QString type = "field");
QJsonObject GenerateDNS(bool withLocalhost, QStringList dnsServers);
QJsonObject GenerateAPIEntry(QString tag, bool withHandler = true, bool withLogger = true, bool withStats = true);
//
// Outbound Protocols
OUTBOUNDSETTING GenerateFreedomOUT(QString domainStrategy, QString redirect, int userLevel);
OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP);
OUTBOUNDSETTING GenerateShadowSocksServerOUT(QString email, QString address, int port, QString method, QString password, bool ota, int level);
OUTBOUNDSETTING GenerateShadowSocksOUT(QList<QJsonObject> servers);
OUTBOUNDSETTING GenerateHTTPSOCKSOut(QString address, int port, bool useAuth, QString username, QString password);
//
// Inbounds Protocols
INBOUNDSETTING GenerateDokodemoIN(QString address, int port, QString network, int timeout, bool followRedirect, int userLevel);
INBOUNDSETTING GenerateHTTPIN(QList<AccountObject> accounts, int timeout = 300, bool allowTransparent = true, int userLevel = 0);
INBOUNDSETTING GenerateSocksIN(QString auth, QList<AccountObject> _accounts, bool udp = false, QString ip = "127.0.0.1", int userLevel = 0);
//
// Generate FINAL Configs
CONFIGROOT GenerateRuntimeConfig(CONFIGROOT root);
OUTBOUND GenerateOutboundEntry(QString protocol, OUTBOUNDSETTING settings, QJsonObject streamSettings, QJsonObject mux = QJsonObject(), QString sendThrough = "0.0.0.0", QString tag = "");
INBOUND GenerateInboundEntry(QString listen, int port, QString protocol, INBOUNDSETTING settings, QString tag, QJsonObject sniffing = QJsonObject(), QJsonObject allocate = QJsonObject());
}
}
}
using namespace Qv2ray::ConfigOperations;
#endif // CONFIGGENERATION_H

View File

@ -1,334 +0,0 @@
#include "QvCoreConfigOperations.hpp"
namespace Qv2ray
{
namespace ConfigOperations
{
inline namespace Convertion
{
// From https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias)
{
QJsonObject vmessUriRoot;
// Constant
vmessUriRoot["v"] = 2;
vmessUriRoot["ps"] = alias;
vmessUriRoot["add"] = serverConfig.address;
vmessUriRoot["port"] = serverConfig.port;
vmessUriRoot["id"] = serverConfig.users.front().id;
vmessUriRoot["aid"] = serverConfig.users.front().alterId;
vmessUriRoot["net"] = transfer.network;
vmessUriRoot["tls"] = transfer.security;
if (transfer.network == "tcp") {
vmessUriRoot["type"] = transfer.tcpSettings.header.type;
} else if (transfer.network == "kcp") {
vmessUriRoot["type"] = transfer.kcpSettings.header.type;
} else if (transfer.network == "quic") {
vmessUriRoot["type"] = transfer.quicSettings.header.type;
vmessUriRoot["host"] = transfer.quicSettings.security;
vmessUriRoot["path"] = transfer.quicSettings.key;
} else if (transfer.network == "ws") {
auto x = transfer.wsSettings.headers;
auto host = x.contains("host");
auto CapHost = x.contains("Host");
auto realHost = host ? x["host"] : (CapHost ? x["Host"] : "");
//
vmessUriRoot["host"] = realHost;
vmessUriRoot["path"] = transfer.wsSettings.path;
} else if (transfer.network == "h2" || transfer.network == "http") {
vmessUriRoot["host"] = transfer.httpSettings.host.join(",");
vmessUriRoot["path"] = transfer.httpSettings.path;
}
//
auto vmessPart = Base64Encode(JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact));
return "vmess://" + vmessPart;
}
QString DecodeSubscriptionString(QByteArray arr)
{
// 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);
}
//
// Save Connection to a place, with checking if there's existing file.
// If so, append "_N" to the name.
bool SaveConnectionConfig(CONFIGROOT obj, QString *alias, bool canOverrideExisting)
{
auto str = JsonToString(obj);
QFile *config = new QFile(QV2RAY_CONFIG_DIR + *alias + QV2RAY_CONFIG_FILE_EXTENSION);
// If there's already a file AND we CANNOT override existing file.
if (config->exists() && !canOverrideExisting) {
// Alias is a pointer to a QString.
DeducePossibleFileName(QV2RAY_CONFIG_DIR, alias, QV2RAY_CONFIG_FILE_EXTENSION);
config = new QFile(QV2RAY_CONFIG_DIR + *alias + QV2RAY_CONFIG_FILE_EXTENSION);
}
LOG(MODULE_CONFIG, "Saving a config named: " + *alias)
return StringToFile(&str, config);
}
bool SaveSubscriptionConfig(CONFIGROOT obj, const QString &subscription, QString *name)
{
auto str = JsonToString(obj);
auto fName = *name;
if (!IsValidFileName(fName)) {
fName = RemoveInvalidFileName(fName);
}
QFile *config = new QFile(QV2RAY_SUBSCRIPTION_DIR + subscription + "/" + fName + QV2RAY_CONFIG_FILE_EXTENSION);
// If there's already a file. THIS IS EXTREMELY RARE
if (config->exists()) {
LOG(MODULE_FILE, "Trying to overrwrite an existing subscription config file. THIS IS RARE")
}
LOG(MODULE_CONFIG, "Saving a subscription named: " + fName)
bool result = StringToFile(&str, config);
if (!result) {
LOG(MODULE_FILE, "Failed to save a connection config from subscription: " + subscription + ", name: " + fName)
}
*name = fName;
return result;
}
bool RemoveConnection(const QString &alias)
{
QFile config(QV2RAY_CONFIG_DIR + alias + QV2RAY_CONFIG_FILE_EXTENSION);
if (!config.exists()) {
LOG(MODULE_FILE, "Trying to remove a non-existing file?")
return false;
} else {
return config.remove();
}
}
bool RemoveSubscriptionConnection(const QString &subsName, const QString &name)
{
QFile config(QV2RAY_SUBSCRIPTION_DIR + subsName + "/" + name + QV2RAY_CONFIG_FILE_EXTENSION);
if (!config.exists()) {
LOG(MODULE_FILE, "Trying to remove a non-existing file?")
return false;
} else {
return config.remove();
}
}
// This generates global config containing only one outbound....
CONFIGROOT ConvertConfigFromVMessString(const QString &vmessStr, QString *alias, QString *errMessage)
{
#define default CONFIGROOT()
LOG(MODULE_CONFIG, "Trying to convert from a vmess string.")
QString vmess = vmessStr;
if (vmess.trimmed() != vmess) {
LOG(MODULE_CONFIG, "VMess string has some prefix/postfix spaces, trimming.")
vmess = vmessStr.trimmed();
}
// Reset errMessage
*errMessage = "";
if (!vmess.toLower().startsWith("vmess://")) {
*errMessage = QObject::tr("VMess string should start with 'vmess://'");
return default;
}
try {
QStringRef vmessJsonB64(&vmess, 8, vmess.length() - 8);
auto b64Str = vmessJsonB64.toString();
if (b64Str.isEmpty()) {
*errMessage = QObject::tr("VMess string should be a valid base64 string");
return default;
}
auto vmessString = Base64Decode(b64Str);
auto jsonErr = VerifyJsonString(vmessString);
if (!jsonErr.isEmpty()) {
*errMessage = jsonErr;
return default;
}
auto vmessConf = JsonFromString(vmessString);
if (vmessConf.isEmpty()) {
*errMessage = QObject::tr("JSON should not be empty");
return default;
}
bool flag = true;
// C is a quick hack...
#define C(k) vmessConf.contains(k)
// id, aid, port and add are mandatory fields of a vmess:// link.
flag = flag && C("id") && C("aid") && C("port") && C("add");
// Stream Settings
auto net = C("net") ? vmessConf["net"].toString() : "tcp";
if (net == "http" || net == "ws")
flag = flag && C("host") && C("path");
else if (net == "domainsocket")
flag = flag && C("path");
else if (net == "quic")
flag = flag && C("host") && C("type") && C("path");
#undef C
//return flag ? 0 : 1;
} catch (exception *e) {
LOG(MODULE_IMPORT, "Failed to decode vmess string: " + QString(e->what()))
*errMessage = e->what();
return default;
}
// --------------------------------------------------------------------------------------
CONFIGROOT root;
auto b64String = QStringRef(&vmess, 8, vmess.length() - 8).toString();
auto vmessConf = JsonFromString(Base64Decode(b64String));
//
QString ps, add, id, net, type, host, path, tls;
int port, aid;
//
// key = key in JSON and the variable name.
// values = Candidate variable list, if not match, the first one is used as default.
// [[val.size() <= 1]] is used when only the default value exists.
// - It can be empty, if so, if the key is not in the JSON, or the value is empty, it'll report an error.
// - Else if it contains one thing. if the key is not in the JSON, or the value is empty, it'll use that one.
// - Else if it contains many things, when the key IS in the JSON but not in those THINGS, it'll use the first one in the THINGS
// - Else, it'll use the value found from the JSON object.
//
#define empty_arg
#define __vmess_checker__func(key, values) \
{\
auto val = QStringList() values;\
if (vmessConf.contains(#key) && !vmessConf[#key].toVariant().toString().trimmed().isEmpty() \
&& (val.size() <= 1 || val.contains(vmessConf[#key].toVariant().toString()))) {\
key = vmessConf[#key].toVariant().toString();\
DEBUG(MODULE_IMPORT, "Found key \"" #key "\" within the vmess object.")\
} else if (!val.isEmpty()) {\
key = val.first(); \
DEBUG(MODULE_IMPORT, "Using key \"" #key "\" from the first candidate list: " + key)\
} else{\
*errMessage = QObject::tr(#key " does not exist."); \
LOG(MODULE_IMPORT, "Cannot process \"" #key "\" since it's not included in the json object." ) \
LOG(MODULE_IMPORT, " --> values: " + val.join(";")) \
LOG(MODULE_IMPORT, " --> PS: " + ps) \
}\
}
// Strict check of VMess protocol, to check if the specified value is in the correct range.
//
// Get Alias (AKA ps) from address and port.
__vmess_checker__func(ps, << vmessConf["add"].toVariant().toString() + ":" + vmessConf["port"].toVariant().toString());
__vmess_checker__func(add, empty_arg)
__vmess_checker__func(id, empty_arg)
__vmess_checker__func(net, << "tcp" << "http" << "h2" << "ws" << "kcp" << "domainsocket" << "quic")
__vmess_checker__func(type, << "none" << "http" << "srtp" << "utp" << "wechat-video")
__vmess_checker__func(path, << "")
__vmess_checker__func(host, << "")
__vmess_checker__func(tls, << "")
//
port = vmessConf["port"].toVariant().toInt();
aid = vmessConf["aid"].toVariant().toInt();
// Apply the settings.
//
// User
VMessServerObject::UserObject user;
user.id = id;
user.alterId = aid;
//
// Server
VMessServerObject serv;
serv.port = port;
serv.address = add;
serv.users.push_back(user);
//
// VMess root config
OUTBOUNDSETTING vConf;
QJsonArray vnextArray;
vnextArray.append(JsonFromString(StructToJsonString(serv)));
vConf["vnext"] = vnextArray;
//
// Stream Settings
StreamSettingsObject streaming;
if (net == "tcp") {
streaming.tcpSettings.header.type = type;
} else if (net == "http" || net == "h2") {
// Fill hosts for HTTP
for (auto _host : host.split(',')) {
streaming.httpSettings.host.push_back(_host.trimmed());
}
streaming.httpSettings.path = path;
} else if (net == "ws") {
streaming.wsSettings.headers["Host"] = host;
streaming.wsSettings.path = path;
} else if (net == "kcp") {
streaming.kcpSettings.header.type = type;
} else if (net == "domainsocket") {
streaming.dsSettings.path = path;
} else if (net == "quic") {
streaming.quicSettings.security = host;
streaming.quicSettings.header.type = type;
streaming.quicSettings.key = path;
}
streaming.security = tls;
//
// Network type
streaming.network = net;
//
// WARN Mux is missing here.
auto outbound = GenerateOutboundEntry("vmess", vConf, GetRootObject(streaming), QJsonObject(), "0.0.0.0", OUTBOUND_TAG_PROXY);
//
root["outbounds"] = QJsonArray() << outbound;
// If previous alias is empty, just the PS is needed, else, append a "_"
*alias = alias->trimmed().isEmpty() ? ps : *alias + "_" + ps;
#undef default
return root;
}
CONFIGROOT ConvertConfigFromFile(QString sourceFilePath, bool keepInbounds)
{
QFile source(sourceFilePath);
if (!source.exists()) {
LOG(MODULE_FILE, "Trying to import from an non-existing file.")
return CONFIGROOT();
}
auto root = CONFIGROOT(JsonFromString(StringFromFile(&source)));
if (!keepInbounds) {
JSON_ROOT_TRY_REMOVE("inbounds")
}
JSON_ROOT_TRY_REMOVE("log")
JSON_ROOT_TRY_REMOVE("api")
JSON_ROOT_TRY_REMOVE("stats")
JSON_ROOT_TRY_REMOVE("dns")
return root;
}
bool RenameConnection(const QString &originalName, const QString &newName)
{
LOG(MODULE_CONFIG, "[RENAME] --> ORIGINAL: " + originalName + ", NEW: " + newName)
return QFile::rename(QV2RAY_CONFIG_DIR + originalName + QV2RAY_CONFIG_FILE_EXTENSION, QV2RAY_CONFIG_DIR + newName + QV2RAY_CONFIG_FILE_EXTENSION);
}
bool RenameSubscription(const QString &originalName, const QString &newName)
{
LOG(MODULE_SUBSCRIPTION, "[RENAME] --> ORIGINAL: " + originalName + ", NEW: " + newName)
return QDir().rename(QV2RAY_SUBSCRIPTION_DIR + originalName, QV2RAY_SUBSCRIPTION_DIR + newName);
}
}
}
}

View File

@ -1,425 +0,0 @@
#include "QvCoreConfigOperations.hpp"
namespace Qv2ray
{
namespace ConfigOperations
{
inline namespace Generation
{
// Important config generation algorithms.
static const QStringList vLogLevels = {"none", "debug", "info", "warning", "error"};
// -------------------------- BEGIN CONFIG GENERATIONS ----------------------------------------------------------------------------
ROUTING GenerateRoutes(bool enableProxy, bool proxyCN)
{
ROUTING root;
root.insert("domainStrategy", "IPIfNonMatch");
//
// For Rules list
ROUTERULELIST rulesList;
if (!enableProxy) {
// This is added to disable all proxies, as a alternative influence of #64
rulesList.append(GenerateSingleRouteRule(QStringList() << "regexp:.*", true, OUTBOUND_TAG_DIRECT));
}
// Private IPs should always NOT TO PROXY!
rulesList.append(GenerateSingleRouteRule(QStringList() << "geoip:private", false, OUTBOUND_TAG_DIRECT));
//
// Check if CN needs proxy, or direct.
rulesList.append(GenerateSingleRouteRule(QStringList() << "geoip:cn", false, proxyCN ? OUTBOUND_TAG_DIRECT : OUTBOUND_TAG_PROXY));
rulesList.append(GenerateSingleRouteRule(QStringList() << "geosite:cn", true, proxyCN ? OUTBOUND_TAG_DIRECT : OUTBOUND_TAG_PROXY));
//
// As a bug fix of #64, this default rule has been disabled.
//rulesList.append(GenerateSingleRouteRule(QStringList({"regexp:.*"}), true, globalProxy ? OUTBOUND_TAG_PROXY : OUTBOUND_TAG_DIRECT));
root.insert("rules", rulesList);
RROOT
}
ROUTERULE GenerateSingleRouteRule(QStringList list, bool isDomain, QString outboundTag, QString type)
{
ROUTERULE root;
root.insert(isDomain ? "domain" : "ip", QJsonArray::fromStringList(list));
JADD(outboundTag, type)
RROOT
}
OUTBOUNDSETTING GenerateFreedomOUT(QString domainStrategy, QString redirect, int userLevel)
{
OUTBOUNDSETTING root;
JADD(domainStrategy, redirect, userLevel)
RROOT
}
OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP)
{
OUTBOUNDSETTING root;
QJsonObject resp;
resp.insert("type", useHTTP ? "http" : "none");
root.insert("response", resp);
RROOT
}
OUTBOUNDSETTING GenerateShadowSocksOUT(QList<QJsonObject> servers)
{
OUTBOUNDSETTING root;
QJsonArray x;
foreach (auto server, servers) {
x.append(server);
}
root.insert("servers", x);
RROOT
}
OUTBOUNDSETTING GenerateShadowSocksServerOUT(QString email, QString address, int port, QString method, QString password, bool ota, int level)
{
OUTBOUNDSETTING root;
JADD(email, address, port, method, password, level, ota)
RROOT
}
QJsonObject GenerateDNS(bool withLocalhost, QStringList dnsServers)
{
QJsonObject root;
QJsonArray servers(QJsonArray::fromStringList(dnsServers));
if (withLocalhost) {
// https://github.com/lhy0403/Qv2ray/issues/64
// The fix patch didn't touch this line below.
//
// Should we APPEND localhost or PUSH_FRONT localhost?
servers.append("localhost");
}
root.insert("servers", servers);
RROOT
}
INBOUNDSETTING GenerateDokodemoIN(QString address, int port, QString network, int timeout, bool followRedirect, int userLevel)
{
INBOUNDSETTING root;
JADD(address, port, network, timeout, followRedirect, userLevel)
RROOT
}
INBOUNDSETTING GenerateHTTPIN(QList<AccountObject> _accounts, int timeout, bool allowTransparent, int userLevel)
{
INBOUNDSETTING root;
QJsonArray accounts;
foreach (auto account, _accounts) {
if (account.user.isEmpty() && account.pass.isEmpty()) {
continue;
}
accounts.append(GetRootObject(account));
}
if (!accounts.isEmpty()) {
JADD(accounts)
}
JADD(timeout, allowTransparent, userLevel)
RROOT
}
OUTBOUNDSETTING GenerateHTTPSOCKSOut(QString address, int port, bool useAuth, QString username, QString password)
{
OUTBOUNDSETTING root;
QJsonArray servers;
{
QJsonObject oneServer;
oneServer["address"] = address;
oneServer["port"] = port;
if (useAuth) {
QJsonArray users;
QJsonObject oneUser;
oneUser["user"] = username;
oneUser["pass"] = password;
users.push_back(oneUser);
oneServer["users"] = users;
}
servers.push_back(oneServer);
}
JADD(servers)
RROOT
}
INBOUNDSETTING GenerateSocksIN(QString auth, QList<AccountObject> _accounts, bool udp, QString ip, int userLevel)
{
INBOUNDSETTING root;
QJsonArray accounts;
foreach (auto acc, _accounts) {
if (acc.user.isEmpty() && acc.pass.isEmpty()) {
continue;
}
accounts.append(GetRootObject(acc));
}
if (!accounts.isEmpty()) {
JADD(accounts)
}
if (udp) {
JADD(auth, udp, ip, userLevel)
} else {
JADD(auth, userLevel)
}
RROOT
}
OUTBOUND GenerateOutboundEntry(QString protocol, OUTBOUNDSETTING settings, QJsonObject streamSettings, QJsonObject mux, QString sendThrough, QString tag)
{
OUTBOUND root;
JADD(sendThrough, protocol, settings, tag, streamSettings, mux)
RROOT
}
INBOUND GenerateInboundEntry(QString listen, int port, QString protocol, INBOUNDSETTING settings, QString tag, QJsonObject sniffing, QJsonObject allocate)
{
INBOUND root;
LOG(MODULE_CONNECTION, "allocation is not used here.")
Q_UNUSED(allocate)
JADD(listen, port, protocol, settings, tag, sniffing)
RROOT
}
QJsonObject GenerateAPIEntry(QString tag, bool withHandler, bool withLogger, bool withStats)
{
QJsonObject root;
QJsonArray services;
if (withHandler)
services << "HandlerService";
if (withLogger)
services << "LoggerService";
if (withStats)
services << "StatsService";
JADD(services, tag)
RROOT
}
// -------------------------- END CONFIG GENERATIONS ------------------------------------------------------------------------------
// BEGIN RUNTIME CONFIG GENERATION
CONFIGROOT GenerateRuntimeConfig(CONFIGROOT root)
{
auto gConf = GetGlobalConfig();
QJsonObject logObject;
//
//logObject.insert("access", QV2RAY_CONFIG_PATH + QV2RAY_VCORE_LOG_DIRNAME + QV2RAY_VCORE_ACCESS_LOG_FILENAME);
//logObject.insert("error", QV2RAY_CONFIG_PATH + QV2RAY_VCORE_LOG_DIRNAME + QV2RAY_VCORE_ERROR_LOG_FILENAME);
//
logObject.insert("loglevel", vLogLevels[gConf.logLevel]);
root.insert("log", logObject);
//
auto dnsList = gConf.connectionConfig.dnsList;
auto dnsObject = GenerateDNS(gConf.connectionConfig.withLocalDNS, dnsList);
root.insert("dns", dnsObject);
//
//
// If inbounds list is empty we append our global configured inbounds to the config.
if (!root.contains("inbounds") || root["inbounds"].toArray().empty()) {
INBOUNDS inboundsList;
// HTTP Inbound
if (gConf.inboundConfig.useHTTP) {
INBOUND httpInBoundObject;
httpInBoundObject.insert("listen", gConf.inboundConfig.listenip);
httpInBoundObject.insert("port", gConf.inboundConfig.http_port);
httpInBoundObject.insert("protocol", "http");
httpInBoundObject.insert("tag", "http_IN");
if (gConf.inboundConfig.http_useAuth) {
auto httpInSettings = GenerateHTTPIN(QList<AccountObject>() << gConf.inboundConfig.httpAccount);
httpInBoundObject.insert("settings", httpInSettings);
}
inboundsList.append(httpInBoundObject);
}
// SOCKS Inbound
if (gConf.inboundConfig.useSocks) {
INBOUND socksInBoundObject;
socksInBoundObject.insert("listen", gConf.inboundConfig.listenip);
socksInBoundObject.insert("port", gConf.inboundConfig.socks_port);
socksInBoundObject.insert("protocol", "socks");
socksInBoundObject.insert("tag", "socks_IN");
auto socksInSettings = GenerateSocksIN(gConf.inboundConfig.socks_useAuth ? "password" : "noauth",
QList<AccountObject>() << gConf.inboundConfig.socksAccount,
gConf.inboundConfig.socksUDP,
gConf.inboundConfig.socksLocalIP);
socksInBoundObject.insert("settings", socksInSettings);
inboundsList.append(socksInBoundObject);
}
root["inbounds"] = inboundsList;
DEBUG(MODULE_CONFIG, "Added global config inbounds to the config")
}
// Process every inbounds to make sure a tag is configured, fixed API 0 speed
// issue when no tag is configured.
INBOUNDS newTaggedInbounds = INBOUNDS(root["inbounds"].toArray());
for (auto i = 0; i < newTaggedInbounds.count(); i++) {
auto _inboundItem = newTaggedInbounds[i].toObject();
if (!_inboundItem.contains("tag") || _inboundItem["tag"].toString().isEmpty()) {
LOG(MODULE_CONFIG, "Adding a tag to an inbound.")
_inboundItem["tag"] = GenerateRandomString(8);
newTaggedInbounds[i] = _inboundItem;
}
}
root["inbounds"] = newTaggedInbounds;
//
//
// Note: The part below always makes the whole functionality in trouble......
// BE EXTREME CAREFUL when changing these code below...
// See: https://github.com/lhy0403/Qv2ray/issues/129
// routeCountLabel in Mainwindow makes here failed to ENOUGH-ly check the routing tables
bool isComplex = CheckIsComplexConfig(root);
if (isComplex) {
// For some config files that has routing entries already.
// We DO NOT add extra routings.
//
// HOWEVER, we need to verify the QV2RAY_RULE_ENABLED entry.
// And what's more, process (by removing unused items) from a rule object.
ROUTING routing = ROUTING(root["routing"].toObject());
ROUTERULELIST rules;
LOG(MODULE_CONNECTION, "Processing an existing routing table.")
for (auto _rule : routing["rules"].toArray()) {
auto _b = _rule.toObject();
if (_b.contains("QV2RAY_RULE_USE_BALANCER")) {
if (_b["QV2RAY_RULE_USE_BALANCER"].toBool()) {
// We use balancer
_b.remove("outboundTag");
} else {
// We only use the normal outbound
_b.remove("balancerTag");
}
} else {
LOG(MODULE_CONFIG, "We found a rule without QV2RAY_RULE_USE_BALANCER, so don't process it.")
}
// If this entry has been disabled.
if (_b.contains("QV2RAY_RULE_ENABLED") && _b["QV2RAY_RULE_ENABLED"].toBool() == true) {
rules.append(_b);
} else {
LOG(MODULE_CONFIG, "Discarded a rule as it's been set DISABLED")
}
}
routing["rules"] = rules;
root["routing"] = routing;
} else {
//
LOG(MODULE_CONNECTION, "Inserting default values to simple config")
if (root["outbounds"].toArray().count() != 1) {
// There are no ROUTING but 2 or more outbounds.... This is rare, but possible.
LOG(MODULE_CONNECTION, "WARN: This message usually indicates the config file has logic errors:")
LOG(MODULE_CONNECTION, "WARN: --> The config file has NO routing section, however more than 1 outbounds are detected.")
}
auto routeObject = GenerateRoutes(gConf.connectionConfig.enableProxy, gConf.connectionConfig.bypassCN);
root.insert("routing", routeObject);
//
// Process forward proxy
#define fpConf gConf.connectionConfig.forwardProxyConfig
if (fpConf.enableForwardProxy) {
auto outboundArray = root["outbounds"].toArray();
auto firstOutbound = outboundArray.first().toObject();
if (firstOutbound[QV2RAY_USE_FPROXY_KEY].toBool(false)) {
LOG(MODULE_CONNECTION, "Applying forward proxy to current connection.")
auto proxy = PROXYSETTING();
proxy["tag"] = OUTBOUND_TAG_FORWARD_PROXY;
firstOutbound["proxySettings"] = proxy;
// FP Outbound.
OUTBOUNDSETTING fpOutbound;
if (fpConf.type.toLower() == "http" || fpConf.type.toLower() == "socks") {
fpOutbound = GenerateHTTPSOCKSOut(fpConf.serverAddress, fpConf.port, fpConf.useAuth, fpConf.username, fpConf.password);
outboundArray.push_back(GenerateOutboundEntry(fpConf.type.toLower(), fpOutbound, QJsonObject(), QJsonObject(), "0.0.0.0", OUTBOUND_TAG_FORWARD_PROXY));
} else {
LOG(MODULE_CONNECTION, "WARNING: Unsupported outbound type: " + fpConf.type)
}
} else {
// Remove proxySettings from firstOutbound
firstOutbound.remove("proxySettings");
}
outboundArray.replace(0, firstOutbound);
root["outbounds"] = outboundArray;
}
#undef fpConf
OUTBOUNDS outbounds = OUTBOUNDS(root["outbounds"].toArray());
outbounds.append(GenerateOutboundEntry("freedom", GenerateFreedomOUT("AsIs", ":0", 0), QJsonObject(), QJsonObject(), "0.0.0.0", OUTBOUND_TAG_DIRECT));
root["outbounds"] = outbounds;
}
// Let's process some api features.
{
//
// Stats
//
root.insert("stats", QJsonObject());
//
// Routes
//
QJsonObject routing = root["routing"].toObject();
QJsonArray routingRules = routing["rules"].toArray();
QJsonObject APIRouteRoot;
APIRouteRoot["type"] = "field";
APIRouteRoot["outboundTag"] = API_TAG_DEFAULT;
QJsonArray inboundTag;
inboundTag.append(API_TAG_INBOUND);
APIRouteRoot["inboundTag"] = inboundTag;
// Add this to root.
routingRules.push_front(APIRouteRoot);
routing["rules"] = routingRules;
root["routing"] = routing;
//
// Policy
//
QJsonObject policyRoot = root.contains("policy") ? root["policy"].toObject() : QJsonObject();
QJsonObject systemPolicy = policyRoot.contains("system") ? policyRoot["system"].toObject() : QJsonObject();
systemPolicy["statsInboundUplink"] = true;
systemPolicy["statsInboundDownlink"] = true;
policyRoot["system"] = systemPolicy;
// Add this to root.
root["policy"] = policyRoot;
//
// Inbounds
//
INBOUNDS inbounds = INBOUNDS(root["inbounds"].toArray());
INBOUNDSETTING fakeDocodemoDoor;
fakeDocodemoDoor["address"] = "127.0.0.1";
QJsonObject apiInboundsRoot = GenerateInboundEntry("127.0.0.1", gConf.apiConfig.statsPort, "dokodemo-door", fakeDocodemoDoor, API_TAG_INBOUND);
inbounds.push_front(apiInboundsRoot);
root["inbounds"] = inbounds;
//
// API
//
root["api"] = GenerateAPIEntry(API_TAG_DEFAULT);
}
return root;
}
}
}
}

View File

@ -1,109 +0,0 @@
#include "QvUtils.hpp"
#include "QApplication"
namespace Qv2ray
{
namespace Utils
{
static bool _isQv2rayExiting = false;
static Qv2rayConfig GlobalConfig;
static QString ConfigDirPath;
void SetGlobalConfig(Qv2rayConfig conf)
{
GlobalConfig = conf;
QFile config(QV2RAY_CONFIG_FILE);
QString str = StructToJsonString(GetGlobalConfig());
StringToFile(&str, &config);
}
Qv2rayConfig GetGlobalConfig()
{
return GlobalConfig;
}
QString GetConfigDirPath()
{
return ConfigDirPath;
}
void SetConfigDirPath(const QString *path)
{
ConfigDirPath = *path;
if (!path->endsWith("/")) {
ConfigDirPath += "/";
}
}
void LoadGlobalConfig()
{
QFile file(QV2RAY_CONFIG_FILE);
file.open(QFile::ReadOnly);
QTextStream stream(&file);
auto str = stream.readAll();
auto config = StructFromJsonString<Qv2rayConfig>(str);
SetGlobalConfig(config);
file.close();
}
void ExitQv2ray()
{
_isQv2rayExiting = true;
QApplication::quit();
}
bool isExiting()
{
return _isQv2rayExiting;
}
tuple<QString, int, QString> GetConnectionInfo(const CONFIGROOT &root)
{
bool validOutboundFound = false;
QString host;
int port;
for (auto item : root["outbounds"].toArray()) {
OUTBOUND outBoundRoot = OUTBOUND(item.toObject());
QString outboundType = "";
validOutboundFound = GetOutboundData(outBoundRoot, &host, &port, &outboundType);
if (validOutboundFound) {
return make_tuple(host, port, outboundType);
} else {
LOG(MODULE_UI, "Unknown outbound entry: " + outboundType + ", cannot deduce host and port.")
}
}
return make_tuple(QObject::tr("N/A"), 0, QObject::tr("N/A"));
}
bool GetOutboundData(const OUTBOUND &out, QString *host, int *port, QString *protocol)
{
// Set initial values.
*host = QObject::tr("N/A");
*port = 0;
*protocol = out["protocol"].toString(QObject::tr("N/A"));
if (*protocol == "vmess") {
auto Server = StructFromJsonString<VMessServerObject>(JsonToString(out["settings"].toObject()["vnext"].toArray().first().toObject()));
*host = Server.address;
*port = Server.port;
return true;
} else if (*protocol == "shadowsocks") {
auto x = JsonToString(out["settings"].toObject()["servers"].toArray().first().toObject());
auto Server = StructFromJsonString<ShadowSocksServerObject>(x);
*host = Server.address;
*port = Server.port;
return true;
} else if (*protocol == "socks") {
auto x = JsonToString(out["settings"].toObject()["servers"].toArray().first().toObject());
auto Server = StructFromJsonString<SocksServerObject>(x);
*host = Server.address;
*port = Server.port;
return true;
} else {
return false;
}
}
}
}

View File

@ -1,38 +0,0 @@
#ifndef UTILS_H
#define UTILS_H
#include "Qv2rayBase.hpp"
#include "QvHelpers.hpp"
#include "QvCore/QvCommandLineArgs.hpp"
namespace Qv2ray
{
namespace Utils
{
void SetConfigDirPath(const QString *path);
QString GetConfigDirPath();
void SetGlobalConfig(Qv2rayConfig conf);
Qv2rayConfig GetGlobalConfig();
void LoadGlobalConfig();
inline QString getTag(const INBOUND &in)
{
return in["tag"].toString();
}
inline QString getTag(const OUTBOUND &in)
{
return in["tag"].toString();
}
void ExitQv2ray();
bool isExiting();
tuple<QString, int, QString> GetConnectionInfo(const CONFIGROOT &alias);
bool GetOutboundData(const OUTBOUND &out, QString *host, int *port, QString *protocol);
}
}
using namespace Qv2ray::Utils;
#endif // UTILS_H

View File

@ -0,0 +1,26 @@
#pragma once
#include "base/models/QvRuntimeConfig.hpp"
#include "base/models/QvStartupConfig.hpp"
#include "base/models/QvConfigModel.hpp"
#include <QTranslator>
// Instantiation for Qv2ray global objects.
#ifdef QT_DEBUG
const bool isDebugBuild = true;
#else
const bool isDebugBuild = false;
#endif
namespace Qv2ray
{
// Qv2ray runtime config
inline bool isExiting = false;
inline QString Qv2rayConfigPath = "/";
inline base::Qv2rayRuntimeConfig RuntimeConfig = base::Qv2rayRuntimeConfig();
inline base::config::Qv2rayConfig GlobalConfig = base::config::Qv2rayConfig();
inline base::QvStartupOptions StartupOption = base::QvStartupOptions();
//
inline std::unique_ptr<QTranslator> Qv2rayTranslator;
}

View File

@ -1,5 +1,4 @@
#ifndef QJSONMACROS_H #pragma once
#define QJSONMACROS_H
#define STRINGIZE(arg) STRINGIZE1(arg) #define STRINGIZE(arg) STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg) #define STRINGIZE1(arg) STRINGIZE2(arg)
@ -47,5 +46,3 @@
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__) #define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
#define RROOT return root; #define RROOT return root;
#endif

131
src/base/Qv2rayBase.hpp Normal file
View File

@ -0,0 +1,131 @@
#pragma once
//
#ifndef XTOSTRUCT_QT
# define XTOSTRUCT_QT
#endif
//
#include <QtCore>
#include <QtGui>
#include <QApplication>
#include <QMap>
#include <vector>
#include <algorithm>
#include <ctime>
// Base support.
#include "base/Qv2rayLog.hpp"
#include "base/Qv2rayFeatures.hpp"
#include "base/JsonHelpers.hpp"
#include "base/GlobalInstances.hpp"
// Code Models
#include "base/models/QvSafeType.hpp"
#include "base/models/CoreObjectModels.hpp"
#include "base/models/QvConfigModel.hpp"
#include "base/models/QvConfigIdentifier.hpp"
#include "base/models/QvStartupConfig.hpp"
#include "base/models/QvRuntimeConfig.hpp"
using namespace std;
using namespace std::chrono;
using namespace Qv2ray;
using namespace Qv2ray::base;
using namespace Qv2ray::base::safetype;
using namespace Qv2ray::base::config;
using namespace Qv2ray::base::objects;
using namespace Qv2ray::base::objects::protocol;
using namespace Qv2ray::base::objects::transfer;
// Linux users and DEs should handle the darkMode UI themselves.
#ifndef QV2RAY_USE_BUILTIN_DARKTHEME
# ifndef Q_OS_LINUX
# define QV2RAY_USE_BUILTIN_DARKTHEME
# endif
#endif
#define QV2RAY_BUILD_INFO QString(_QV2RAY_BUILD_INFO_STR_)
#define QV2RAY_BUILD_EXTRA_INFO QString(_QV2RAY_BUILD_EXTRA_INFO_STR_)
extern const bool isDebugBuild;
// Base folder suffix.
#ifdef QT_DEBUG
# define QV2RAY_CONFIG_DIR_SUFFIX "_debug/"
#else
# define QV2RAY_CONFIG_DIR_SUFFIX "/"
#endif
// Get Configured Config Dir Path
#define QV2RAY_CONFIG_DIR (Qv2ray::Qv2rayConfigPath)
#define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf")
#define QV2RAY_SUBSCRIPTION_DIR (QV2RAY_CONFIG_DIR + "subscriptions/")
// Get GFWList and PAC file path.
#define QV2RAY_RULES_DIR (QV2RAY_CONFIG_DIR + "rules/")
#define QV2RAY_RULES_GFWLIST_PATH (QV2RAY_RULES_DIR + "gfwList.txt")
#define QV2RAY_RULES_PAC_PATH (QV2RAY_RULES_DIR + "pac.txt")
#define QV2RAY_CONFIG_FILE_EXTENSION ".qv2ray.json"
#define QV2RAY_GENERATED_DIR (QV2RAY_CONFIG_DIR + "generated/")
#define QV2RAY_GENERATED_FILE_PATH (QV2RAY_GENERATED_DIR + "config.gen.json")
#if ! defined (QV2RAY_DEFAULT_VCORE_PATH) && ! defined (QV2RAY_DEFAULT_VASSETS_PATH)
# define QV2RAY_DEFAULT_VASSETS_PATH (QV2RAY_CONFIG_DIR + "vcore/")
# ifdef Q_OS_WIN
# define QV2RAY_DEFAULT_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray.exe")
# else
# define QV2RAY_DEFAULT_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray")
# endif
#elif defined (QV2RAY_DEFAULT_VCORE_PATH) && defined (QV2RAY_DEFAULT_VASSETS_PATH)
// ---- Using user-specified VCore and VAssets path
#else
# error Both QV2RAY_DEFAULT_VCORE_PATH and QV2RAY_DEFAULT_VASSETS_PATH need to present when specifying the paths.
#endif
#ifdef Q_OS_WIN
/// There's no tProxy thing on Windows....
//# define QV2RAY_TPROXY_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray.exe")
//# define QV2RAY_TPROXY_VCTL_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ctl.exe")
#else
# define QV2RAY_TPROXY_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray")
# define QV2RAY_TPROXY_VCTL_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ctl")
#endif
#define QV2RAY_VCORE_LOG_DIRNAME "logs/"
#define QV2RAY_VCORE_ACCESS_LOG_FILENAME "access.log"
#define QV2RAY_VCORE_ERROR_LOG_FILENAME "error.log"
// GUI TOOLS
#define RED(obj) \
auto _temp = obj->palette(); \
_temp.setColor(QPalette::Text, Qt::red); \
obj->setPalette(_temp);
#define BLACK(obj) \
obj->setPalette(this->palette());
#define QV2RAY_UI_RESOURCES_ROOT ((GlobalConfig.uiConfig.useDarkTheme) ? QStringLiteral(":/assets/icons/ui_dark/") : QStringLiteral(":/assets/icons/ui_light/"))
#define QICON_R(file) QIcon(QV2RAY_UI_RESOURCES_ROOT + file)
#define QSTRN(num) QString::number(num)
#define OUTBOUND_TAG_DIRECT "outBound_DIRECT"
#define OUTBOUND_TAG_PROXY "outBound_PROXY"
#define OUTBOUND_TAG_FORWARD_PROXY "_QV2RAY_FORWARD_PROXY_"
#define API_TAG_DEFAULT "_QV2RAY_API_"
#define API_TAG_INBOUND "_QV2RAY_API_INBOUND_"
#define QV2RAY_USE_FPROXY_KEY "_QV2RAY_USE_GLOBAL_FORWARD_PROXY_"
#define JSON_ROOT_TRY_REMOVE(obj) if (root.contains(obj)) { root.remove(obj); }
namespace Qv2ray
{
// Extra header for QvConfigUpgrade.cpp
QJsonObject UpgradeConfig(int fromVersion, int toVersion, QJsonObject root);
inline void ExitQv2ray()
{
isExiting = true;
QApplication::quit();
}
}

View File

@ -0,0 +1,9 @@
#pragma once
// Qv2ray build features.
//
// Always use libgRPC++ on windows platform.
#ifndef WITH_LIB_GRPCPP
# ifdef _WIN32
# define WITH_LIB_GRPCPP
# endif
#endif

52
src/base/Qv2rayLog.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "Qv2rayLog.hpp"
#include "GlobalInstances.hpp"
namespace Qv2ray::base
{
// Forwarded from QvTinyLog
static auto __loggerBuffer = std::make_unique<QStringList>();
static auto __purgerBuffer = std::make_unique<QStringList>();
static QMutex __loggerMutex;
static QMutex __purgerMutex;
void __QV2RAY_LOG_FUNC__(int type, const std::string &func, int line, const QString &module, const QString &log)
{
auto logString = "[" + module + "]: " + log;
auto funcPrepend = QString::fromStdString(func + ":" + to_string(line) + " ");
if (isDebugBuild) {
// Debug build version, we only print info for DEBUG logs and print ALL info when debugLog presents,
if (type == QV2RAY_LOG_DEBUG || StartupOption.debugLog) {
logString = logString.prepend(funcPrepend);
}
} else {
// We only process DEBUG log in Release mode
if (type == QV2RAY_LOG_DEBUG) {
if (StartupOption.debugLog) {
logString = logString.prepend(funcPrepend);
} else {
// Discard debug log in non-debug Qv2ray version with no-debugLog mode.
return;
}
}
}
cout << logString.toStdString() << endl;
{
QMutexLocker _(&__loggerMutex);
__loggerBuffer->append(logString + NEWLINE);
}
}
const QString readLastLog()
{
QMutexLocker _(&__purgerMutex);
{
QMutexLocker _(&__loggerMutex);
__loggerBuffer.swap(__purgerBuffer);
}
auto result = __purgerBuffer->join("");
__purgerBuffer->clear();
return result;
}
}

51
src/base/Qv2rayLog.hpp Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <iostream>
#include <QtDebug>
#include <QBuffer>
using namespace std;
/*
* Tiny log module.
*/
namespace Qv2ray::base
{
void __QV2RAY_LOG_FUNC__(int type, const std::string &func, int line, const QString &module, const QString &log);
const QString readLastLog();
}
#define NEWLINE "\r\n"
#define QV2RAY_LOG_NORMAL 0
#define QV2RAY_LOG_DEBUG 1
#ifndef Q_OS_WIN
#define Qv2ray_PRETTY_FUNCTION __PRETTY_FUNCTION__
#else
#define Qv2ray_PRETTY_FUNCTION __FUNCSIG__
#endif
#define __LOG_IMPL(LEVEL, MODULE, MSG) __QV2RAY_LOG_FUNC__(LEVEL, Qv2ray_PRETTY_FUNCTION, __LINE__, MODULE, MSG);
#define LOG(MODULE, MSG) __LOG_IMPL(QV2RAY_LOG_NORMAL, (MODULE), (MSG));
#define DEBUG(MODULE, MSG) __LOG_IMPL(QV2RAY_LOG_DEBUG, (MODULE), (MSG));
// Log modules used by Qv2ray
const inline QString INIT = "INIT" ;
const inline QString MESSAGING = "BASE-MESSAGING" ;
const inline QString UI = "CORE-UI" ;
const inline QString GRAPH = "CORE-UI-GRAPH" ;
const inline QString SETTINGS = "CORE-SETTINGS" ;
const inline QString VCORE = "CORE-VCORE" ;
//
const inline QString CONNECTION = "CORE-CONNECTION" ;
const inline QString SUBSCRIPTION = "CORE-SUBSCRIPTION" ;
const inline QString IMPORT = "CORE-IMPORT" ;
const inline QString EXPORT = "CORE-EXPORT" ;
//
const inline QString NETWORK = "COMMON-NETWORK" ;
const inline QString FILEIO = "COMMON-FILEIO" ;
//
const inline QString PROXY = "COMPONENT-PROXY" ;
const inline QString UPDATE = "COMPONENT-UPDATE" ;
const inline QString PLUGIN = "COMPONENT-PLUGIN" ;

View File

@ -0,0 +1,313 @@
#pragma once
#include <QString>
#include <QList>
#include <QMap>
#include "3rdparty/x2struct/x2struct.hpp"
namespace Qv2ray::base::objects
{
//
// Used in config generation
struct AccountObject {
QString user;
QString pass;
XTOSTRUCT(O(user, pass))
};
//
//
struct ApiObject {
QString tag;
QList<QString> services;
ApiObject() : tag("api"), services() {}
XTOSTRUCT(O(tag, services))
};
//
//
struct SystemPolicyObject {
bool statsInboundUplink;
bool statsInboundDownlink;
SystemPolicyObject() : statsInboundUplink(), statsInboundDownlink() {}
XTOSTRUCT(O(statsInboundUplink, statsInboundDownlink))
};
//
//
struct LevelPolicyObject {
int handshake;
int connIdle;
int uplinkOnly;
int downlinkOnly;
bool statsUserUplink;
bool statsUserDownlink;
int bufferSize;
LevelPolicyObject(): handshake(), connIdle(), uplinkOnly(), downlinkOnly(), statsUserUplink(), statsUserDownlink(), bufferSize() {}
XTOSTRUCT(O(handshake, connIdle, uplinkOnly, downlinkOnly, statsUserUplink, statsUserDownlink, bufferSize))
};
//
//
struct PolicyObject {
QMap<QString, LevelPolicyObject> level;
QList<SystemPolicyObject> system;
PolicyObject(): level(), system() {}
XTOSTRUCT(O(level, system))
};
//
//
struct RuleObject {
// Added due to the request of @aliyuchang33
bool QV2RAY_RULE_ENABLED;
bool QV2RAY_RULE_USE_BALANCER;
QString QV2RAY_RULE_TAG;
//
QString type;
QList<QString> domain;
QList<QString> ip;
QString port;
QString network;
QList<QString> source;
QList<QString> user;
QList<QString> inboundTag;
QList<QString> protocol;
QString attrs;
QString outboundTag;
QString balancerTag;
RuleObject() : QV2RAY_RULE_ENABLED(true), QV2RAY_RULE_USE_BALANCER(false), QV2RAY_RULE_TAG("new rule"), type("field"), domain(), ip(), port("1-65535"), network(""), source(), user(), inboundTag(), protocol(), attrs(), outboundTag(""), balancerTag("") {}
XTOSTRUCT(O(QV2RAY_RULE_ENABLED, QV2RAY_RULE_USE_BALANCER, QV2RAY_RULE_TAG, type, domain, ip, port, network, source, user, inboundTag, protocol, attrs, outboundTag, balancerTag))
};
//
//
struct BalancerObject {
QString tag ;
QList<QString> selector;
BalancerObject() : tag(), selector() {}
XTOSTRUCT(O(tag, selector))
};
//
//
namespace transfer
{
struct HTTPRequestObject {
QString version;
QString method;
QList<QString> path;
QMap<QString, QList<QString>> headers;
HTTPRequestObject(): version("1.1"), method("GET"), path(), headers() {}
XTOSTRUCT(O(version, method, path, headers))
};
//
//
struct HTTPResponseObject {
QString version;
QString status;
QString reason;
QMap<QString, QList<QString>> headers;
HTTPResponseObject(): version("1.1"), status("200"), reason("OK"), headers() {}
XTOSTRUCT(O(version, status, reason, headers))
};
//
//
struct TCPHeader_M_Object {
QString type;
HTTPRequestObject request;
HTTPResponseObject response;
TCPHeader_M_Object(): type("none"), request(), response() {}
XTOSTRUCT(O(type, request, response))
};
//
//
struct HeaderObject {
QString type;
HeaderObject(): type("none") {}
XTOSTRUCT(O(type))
};
//
//
struct TCPObject {
TCPHeader_M_Object header;
TCPObject(): header() {}
XTOSTRUCT(O(header))
};
//
//
struct KCPObject {
int mtu = 1350;
int tti = 20;
int uplinkCapacity = 5;
int downlinkCapacity = 20;
bool congestion = false;
int readBufferSize = 1;
int writeBufferSize = 1;
HeaderObject header;
KCPObject(): header() {}
XTOSTRUCT(O(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header))
};
//
//
struct WebSocketObject {
QString path;
QMap<QString, QString> headers;
WebSocketObject(): path("/"), headers() {}
XTOSTRUCT(O(path, headers))
};
//
//
struct HttpObject {
QList<QString> host;
QString path;
HttpObject() : host(), path("/") {}
XTOSTRUCT(O(host, path))
};
//
//
struct DomainSocketObject {
QString path;
DomainSocketObject(): path("/") {}
XTOSTRUCT(O(path))
};
//
//
struct QuicObject {
QString security;
QString key;
HeaderObject header;
QuicObject(): security(""), key(""), header() {}
XTOSTRUCT(O(security, key, header))
};
//
//
struct SockoptObject {
int mark;
bool tcpFastOpen;
QString tproxy;
SockoptObject(): mark(0), tcpFastOpen(false), tproxy("off") {}
XTOSTRUCT(O(mark, tcpFastOpen, tproxy))
};
//
//
struct CertificateObject {
QString usage;
QString certificateFile;
QString keyFile;
QList<QString> certificate;
QList<QString> key;
CertificateObject(): usage(), certificateFile(), keyFile(), certificate(), key() {}
XTOSTRUCT(O(usage, certificateFile, keyFile, certificate, key))
};
//
//
struct TLSObject {
QString serverName;
bool allowInsecure;
QList<QString> alpn;
QList<CertificateObject> certificates;
bool disableSystemRoot;
TLSObject(): serverName(), allowInsecure(), certificates(), disableSystemRoot() {}
XTOSTRUCT(O(serverName, allowInsecure, alpn, certificates, disableSystemRoot))
};
}
//
//
struct SniffingObject {
bool enabled = false;
QList<QString> destOverride;
SniffingObject(): enabled(), destOverride() {}
XTOSTRUCT(O(enabled, destOverride))
};
//
//
struct StreamSettingsObject {
QString network;
QString security;
transfer::SockoptObject sockopt;
transfer::TLSObject tlsSettings;
transfer::TCPObject tcpSettings;
transfer::KCPObject kcpSettings;
transfer::WebSocketObject wsSettings;
transfer::HttpObject httpSettings;
transfer::DomainSocketObject dsSettings;
transfer::QuicObject quicSettings;
StreamSettingsObject(): network("tcp"), security("none"), sockopt(), tlsSettings(), tcpSettings(), kcpSettings(), wsSettings(), httpSettings(), dsSettings(), quicSettings() {}
XTOSTRUCT(O(network, security, sockopt, tcpSettings, tlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings))
};
//
//
struct MuxObject {
bool enabled;
int concurrency;
MuxObject(): enabled(), concurrency() {}
XTOSTRUCT(O(enabled, concurrency))
};
//
// Some protocols from: https://v2ray.com/chapter_02/02_protocols.html
namespace protocol
{
// DNS, OutBound
struct DNSOut {
QString network;
QString address;
int port;
DNSOut(): network(""), address("0.0.0.0"), port(0) {}
XTOSTRUCT(O(network, address, port))
};
//
// MTProto, InBound || OutBound
struct MTProtoIn {
struct UserObject {
QString email;
int level;
QString secret;
UserObject() : email("user@domain.com"), level(0), secret("") {}
XTOSTRUCT(O(email, level, secret))
};
QList<UserObject> users;
XTOSTRUCT(O(users))
};
//
// Socks, OutBound
struct SocksServerObject {
struct UserObject {
QString user;
QString pass;
int level;
UserObject(): user("username"), pass("password"), level(0) {}
XTOSTRUCT(O(user, pass, level))
};
QString address;
int port;
QList<UserObject> users;
SocksServerObject(): address("0.0.0.0"), port(0), users() {}
XTOSTRUCT(O(address, port, users))
};
//
// VMess Server
struct VMessServerObject {
struct UserObject {
QString id;
int alterId;
QString security;
int level;
UserObject() : id(""), alterId(64), security("auto"), level(0) {}
XTOSTRUCT(O(id, alterId, security, level))
};
QString address;
int port;
QList<UserObject> users;
VMessServerObject(): address(""), port(0), users() {}
XTOSTRUCT(O(address, port, users))
};
//
// ShadowSocks Server
struct ShadowSocksServerObject {
QString email;
QString address;
QString method;
QString password;
bool ota;
int level;
int port;
ShadowSocksServerObject(): email("user@domain.com"), address("0.0.0.0"), method("aes-256-cfb"), password(""), ota(false), level(0), port(0) {}
XTOSTRUCT(O(email, address, port, method, password, ota, level))
};
}
}

View File

@ -0,0 +1,56 @@
#pragma once
#include <QString>
#include <QtCore>
#include "3rdparty/x2struct/x2struct.hpp"
namespace Qv2ray::base
{
struct ConnectionIdentifier {
QString subscriptionName;
QString connectionName;
ConnectionIdentifier() { };
bool isEmpty()
{
return connectionName.isEmpty();
}
ConnectionIdentifier(QString connectionName)
{
this->connectionName = connectionName;
}
ConnectionIdentifier(QString connectionName, QString subscriptionName)
{
this->connectionName = connectionName;
this->subscriptionName = subscriptionName;
}
const QString IdentifierString() const
{
return connectionName + (subscriptionName.isEmpty() ? "" : " (" + subscriptionName + ")");
}
friend bool operator==(ConnectionIdentifier &left, ConnectionIdentifier &right)
{
return left.subscriptionName == right.subscriptionName && left.connectionName == right.connectionName;
}
friend bool operator!=(ConnectionIdentifier &left, ConnectionIdentifier &right)
{
return !(left == right);
}
friend bool operator==(ConnectionIdentifier &left, QString &right)
{
return left.IdentifierString() == right;
}
friend bool operator!=(ConnectionIdentifier &left, QString &right)
{
return !(left.IdentifierString() == right);
}
// To make QMap happy
friend bool operator<(const ConnectionIdentifier left, const ConnectionIdentifier right)
{
return left.IdentifierString() < right.IdentifierString();
}
friend bool operator>(const ConnectionIdentifier left, const ConnectionIdentifier right)
{
return left.IdentifierString() > right.IdentifierString();
}
XTOSTRUCT(O(subscriptionName, connectionName))
};
}
Q_DECLARE_METATYPE(Qv2ray::base::ConnectionIdentifier);

View File

@ -1,129 +1,56 @@
#ifndef QV2RAYBASE_H #pragma once
#define QV2RAYBASE_H #include "3rdparty/x2struct/x2struct.hpp"
#include <QtCore> #include "base/models/CoreObjectModels.hpp"
#include <QtGui> #include "base/models/QvConfigIdentifier.hpp"
#include <QMap> #include <chrono>
#include <vector>
#include <algorithm>
#include <ctime>
#include "QvTinyLog.hpp"
#include "QvCoreConfigObjects.hpp"
const int QV2RAY_CONFIG_VERSION = 8; const int QV2RAY_CONFIG_VERSION = 8;
// Linux users and DEs should handle the darkMode UI themselves. namespace Qv2ray::base
#ifndef QV2RAY_USE_BUILTIN_DARKTHEME
# ifndef Q_OS_LINUX
# define QV2RAY_USE_BUILTIN_DARKTHEME
# endif
#endif
extern bool isDebug;
// Base folder suffix.
#ifdef QT_DEBUG
# define QV2RAY_CONFIG_DIR_SUFFIX "_debug/"
#else
# define QV2RAY_CONFIG_DIR_SUFFIX "/"
#endif
// Get Configured Config Dir Path
#define QV2RAY_CONFIG_DIR (Qv2ray::Utils::GetConfigDirPath())
#define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf")
#define QV2RAY_SUBSCRIPTION_DIR (QV2RAY_CONFIG_DIR + "subscriptions/")
// Get GFWList and PAC file path.
#define QV2RAY_RULES_DIR (QV2RAY_CONFIG_DIR + "rules/")
#define QV2RAY_RULES_GFWLIST_PATH (QV2RAY_RULES_DIR + "gfwList.txt")
#define QV2RAY_RULES_PAC_PATH (QV2RAY_RULES_DIR + "pac.txt")
#define QV2RAY_CONFIG_FILE_EXTENSION ".qv2ray.json"
#define QV2RAY_GENERATED_DIR (QV2RAY_CONFIG_DIR + "generated/")
#define QV2RAY_GENERATED_FILE_PATH (QV2RAY_GENERATED_DIR + "config.gen.json")
#if ! defined (QV2RAY_DEFAULT_VCORE_PATH) && ! defined (QV2RAY_DEFAULT_VASSETS_PATH)
# define QV2RAY_DEFAULT_VASSETS_PATH (QV2RAY_CONFIG_DIR + "vcore/")
# ifdef Q_OS_WIN
# define QV2RAY_DEFAULT_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray.exe")
# else
# define QV2RAY_DEFAULT_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray")
# endif
#elif defined (QV2RAY_DEFAULT_VCORE_PATH) && defined (QV2RAY_DEFAULT_VASSETS_PATH)
// ---- Using user-specified VCore and VAssets path
#else
# error Both QV2RAY_DEFAULT_VCORE_PATH and QV2RAY_DEFAULT_VASSETS_PATH need to present when specifying the paths.
#endif
#define QV2RAY_VCORE_LOG_DIRNAME "logs/"
#define QV2RAY_VCORE_ACCESS_LOG_FILENAME "access.log"
#define QV2RAY_VCORE_ERROR_LOG_FILENAME "error.log"
// GUI TOOLS
#define QV2RAY_IS_DARKTHEME (GetGlobalConfig().uiConfig.useDarkTheme)
#define RED(obj) \
auto _temp = obj->palette(); \
_temp.setColor(QPalette::Text, Qt::red); \
obj->setPalette(_temp);
#define BLACK(obj) \
obj->setPalette(this->palette());
#define QV2RAY_UI_RESOURCES_ROOT (QV2RAY_IS_DARKTHEME ? QStringLiteral(":/assets/icons/ui_dark/") : QStringLiteral(":/assets/icons/ui_light/"))
#define QICON_R(file) QIcon(QV2RAY_UI_RESOURCES_ROOT + file)
#define QSTRN(num) QString::number(num)
#define NEWLINE "\r\n"
using namespace std;
using namespace std::chrono;
namespace Qv2ray
{ {
// namespace config
// Extra header for QvConfigUpgrade.cpp
QJsonObject UpgradeConfig(int fromVersion, int toVersion, QJsonObject root);
struct QvBarLine {
QString Family;
bool Bold;
bool Italic;
int ColorA;
int ColorR;
int ColorG;
int ColorB;
int ContentType;
double Size;
QString Message;
QvBarLine()
: Family("Consolas")
, Bold(true)
, Italic(false)
, ColorA(255), ColorR(255), ColorG(255), ColorB(255)
, ContentType(0)
, Size(9),
Message("") { }
XTOSTRUCT(O(Bold, Italic, ColorA, ColorR, ColorG, ColorB, Size, Family, Message, ContentType))
};
struct QvBarPage {
int OffsetYpx;
QList<QvBarLine> Lines;
QvBarPage() : OffsetYpx(5) { }
XTOSTRUCT(O(OffsetYpx, Lines))
};
struct Qv2rayToolBarConfig {
QList<QvBarPage> Pages;
XTOSTRUCT(O(Pages))
};
namespace QvConfigModels
{ {
struct QvBarLine {
QString Family;
bool Bold;
bool Italic;
int ColorA;
int ColorR;
int ColorG;
int ColorB;
int ContentType;
double Size;
QString Message;
QvBarLine()
: Family("Consolas")
, Bold(true)
, Italic(false)
, ColorA(255), ColorR(255), ColorG(255), ColorB(255)
, ContentType(0)
, Size(9),
Message("") { }
XTOSTRUCT(O(Bold, Italic, ColorA, ColorR, ColorG, ColorB, Size, Family, Message, ContentType))
};
struct QvBarPage {
int OffsetYpx;
QList<QvBarLine> Lines;
QvBarPage() : OffsetYpx(5) { }
XTOSTRUCT(O(OffsetYpx, Lines))
};
struct Qv2rayToolBarConfig {
QList<QvBarPage> Pages;
XTOSTRUCT(O(Pages))
};
struct Qv2raySubscriptionConfig { struct Qv2raySubscriptionConfig {
time_t lastUpdated; time_t lastUpdated;
float updateInterval; float updateInterval;
QString address; QString address;
Qv2raySubscriptionConfig() : lastUpdated(system_clock::to_time_t(system_clock::now())), updateInterval(5), address("") { } Qv2raySubscriptionConfig() : lastUpdated(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())), updateInterval(5), address("") { }
XTOSTRUCT(O(lastUpdated, updateInterval, address)) XTOSTRUCT(O(lastUpdated, updateInterval, address))
}; };
@ -165,12 +92,12 @@ namespace Qv2ray
bool socks_useAuth; bool socks_useAuth;
bool socksUDP; bool socksUDP;
QString socksLocalIP; QString socksLocalIP;
AccountObject socksAccount; objects::AccountObject socksAccount;
// HTTP // HTTP
bool useHTTP; bool useHTTP;
int http_port; int http_port;
bool http_useAuth; bool http_useAuth;
AccountObject httpAccount; objects::AccountObject httpAccount;
Qv2rayInboundsConfig(): Qv2rayInboundsConfig():
listenip("127.0.0.1"), setSystemProxy(false), pacConfig(), listenip("127.0.0.1"), setSystemProxy(false), pacConfig(),
@ -186,7 +113,7 @@ namespace Qv2ray
bool useDarkTheme; bool useDarkTheme;
bool useDarkTrayIcon; bool useDarkTrayIcon;
int maximumLogLines; int maximumLogLines;
Qv2rayUIConfig() : theme("Fusion"), language("en-US"), useDarkTheme(false), useDarkTrayIcon(true), maximumLogLines(500) { } Qv2rayUIConfig() : theme("Fusion"), language("en_US"), useDarkTheme(false), useDarkTrayIcon(true), maximumLogLines(500) { }
XTOSTRUCT(O(theme, language, useDarkTheme, useDarkTrayIcon, maximumLogLines)) XTOSTRUCT(O(theme, language, useDarkTheme, useDarkTrayIcon, maximumLogLines))
}; };
@ -215,7 +142,7 @@ namespace Qv2ray
// //
QString v2CorePath; QString v2CorePath;
QString v2AssetsPath; QString v2AssetsPath;
QvConfigIdentifier autoStartConfig; ConnectionIdentifier autoStartConfig;
QString ignoredVersion; QString ignoredVersion;
// //
QList<QString> configs; QList<QString> configs;
@ -253,13 +180,5 @@ namespace Qv2ray
uiConfig, uiConfig,
subscriptions, inboundConfig, connectionConfig, toolBarConfig, apiConfig)) subscriptions, inboundConfig, connectionConfig, toolBarConfig, apiConfig))
}; };
} }
} }
using namespace std;
using namespace Qv2ray;
using namespace Qv2ray::V2ConfigModels;
using namespace Qv2ray::QvConfigModels;
#endif // QV2RAYBASE_H

View File

@ -0,0 +1,12 @@
#pragma once
#define SET_RUNTIME_CONFIG(conf, val) RuntimeConfig.conf = val();
#define RESTORE_RUNTIME_CONFIG(conf, func) func(RuntimeConfig.conf);
namespace Qv2ray::base
{
struct Qv2rayRuntimeConfig {
//
bool screenShotHideQv2ray = false;
};
}

View File

@ -0,0 +1,31 @@
#pragma once
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#define SAFE_TYPEDEF(Base, name) \
class name : public Base { \
public: \
template <class... Args> \
explicit name (Args... args) : Base(args...) {} \
const Base& raw() const { return *this; } \
};
namespace Qv2ray::base::safetype
{
// To prevent anonying QJsonObject misuse
SAFE_TYPEDEF(QJsonObject, INBOUNDSETTING)
SAFE_TYPEDEF(QJsonObject, OUTBOUNDSETTING)
SAFE_TYPEDEF(QJsonObject, INBOUND)
SAFE_TYPEDEF(QJsonObject, OUTBOUND)
SAFE_TYPEDEF(QJsonObject, CONFIGROOT)
SAFE_TYPEDEF(QJsonObject, PROXYSETTING)
//
SAFE_TYPEDEF(QJsonArray, INOUTLIST)
SAFE_TYPEDEF(INOUTLIST, OUTBOUNDS)
SAFE_TYPEDEF(INOUTLIST, INBOUNDS)
SAFE_TYPEDEF(QJsonObject, ROUTING)
SAFE_TYPEDEF(QJsonObject, ROUTERULE)
SAFE_TYPEDEF(QJsonArray, ROUTERULELIST)
}

View File

@ -0,0 +1,18 @@
#pragma once
namespace Qv2ray
{
namespace base
{
struct QvStartupOptions {
/// No API subsystem
bool noAPI;
/// Explicitly run as root user.
bool forceRunAsRootUser;
/// Enable Debug Log.
bool debugLog;
/// Enable Network toolbar plugin.
bool enableToolbarPlguin;
};
}
}

View File

@ -0,0 +1,61 @@
#include "CommandArgs.hpp"
#include "base/Qv2rayBase.hpp"
namespace Qv2ray::common
{
QvCommandArgParser::QvCommandArgParser() : QObject(),
noAPIOption("noAPI", QObject::tr("Disable gRPC API subsystems.")),
runAsRootOption("I-just-wanna-run-with-root", QObject::tr("Explicitly run Qv2ray as root.")),
debugOption("debug", QObject::tr("Enable Debug Output")),
withToolbarOption("withToolbarPlugin", QObject::tr("Enable Qv2ray network toolbar plugin")),
//
helpOption("FAKE"), versionOption("FAKE")
{
parser.setApplicationDescription(QObject::tr("Qv2ray - A cross-platform Qt frontend for V2ray."));
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
//
parser.addOption(noAPIOption);
parser.addOption(runAsRootOption);
parser.addOption(debugOption);
parser.addOption(withToolbarOption);
helpOption = parser.addHelpOption();
versionOption = parser.addVersionOption();
}
CommandLineParseResult QvCommandArgParser::ParseCommandLine(QString *errorMessage)
{
if (!parser.parse(QCoreApplication::arguments())) {
*errorMessage = parser.errorText();
return CommandLineError;
}
if (parser.isSet(versionOption))
return CommandLineVersionRequested;
if (parser.isSet(helpOption))
return CommandLineHelpRequested;
if (parser.isSet(noAPIOption)) {
DEBUG(INIT, "noAPIOption is set.")
StartupOption.noAPI = true;
}
if (parser.isSet(runAsRootOption)) {
DEBUG(INIT, "runAsRootOption is set.")
StartupOption.forceRunAsRootUser = true;
}
if (parser.isSet(debugOption)) {
DEBUG(INIT, "debugOption is set.")
StartupOption.debugLog = true;
}
if (parser.isSet(withToolbarOption)) {
DEBUG(INIT, "withToolbarOption is set.")
StartupOption.enableToolbarPlguin = true;
}
return CommandLineOk;
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <QCommandLineParser>
namespace Qv2ray::common
{
enum CommandLineParseResult {
CommandLineOk,
CommandLineError,
CommandLineVersionRequested,
CommandLineHelpRequested
};
class QvCommandArgParser : public QObject
{
Q_OBJECT
public:
QvCommandArgParser();
CommandLineParseResult ParseCommandLine(QString *errorMessage);
const QCommandLineParser *Parser()
{
return &parser;
}
private:
QCommandLineParser parser;
QCommandLineOption noAPIOption;
QCommandLineOption runAsRootOption;
QCommandLineOption debugOption;
QCommandLineOption withToolbarOption;
QCommandLineOption helpOption;
QCommandLineOption versionOption;
};
}
using namespace Qv2ray::common;

View File

@ -1,8 +1,9 @@
#include "QvHTTPRequestHelper.hpp" #include "HTTPRequestHelper.hpp"
#include <QByteArray> #include <QByteArray>
#include <QNetworkProxy> #include <QNetworkProxy>
#include "base/Qv2rayBase.hpp"
namespace Qv2ray namespace Qv2ray::common
{ {
QvHttpRequestHelper::QvHttpRequestHelper() : reply() QvHttpRequestHelper::QvHttpRequestHelper() : reply()
{ {
@ -18,7 +19,7 @@ namespace Qv2ray
QUrl qUrl = QUrl(url); QUrl qUrl = QUrl(url);
if (!qUrl.isValid()) { if (!qUrl.isValid()) {
LOG(MODULE_NETWORK, "Provided URL is invalid: " + url) LOG(NETWORK, "Provided URL is invalid: " + url)
return false; return false;
} }
@ -28,7 +29,7 @@ namespace Qv2ray
void QvHttpRequestHelper::setHeader(const QByteArray &key, const QByteArray &value) void QvHttpRequestHelper::setHeader(const QByteArray &key, const QByteArray &value)
{ {
DEBUG(MODULE_NETWORK, "Adding HTTP request header: " + key + ":" + value) DEBUG(NETWORK, "Adding HTTP request header: " + key + ":" + value)
request.setRawHeader(key, value); request.setRawHeader(key, value);
} }
@ -43,7 +44,7 @@ namespace Qv2ray
accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy)); accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy));
} }
LOG(MODULE_NETWORK, "Sync get is using system proxy settings") LOG(NETWORK, "Sync get is using system proxy settings")
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
reply = accessManager.get(request); reply = accessManager.get(request);
connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished); connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished);
@ -103,13 +104,13 @@ namespace Qv2ray
void QvHttpRequestHelper::onRequestFinished() void QvHttpRequestHelper::onRequestFinished()
{ {
LOG(MODULE_NETWORK, "Network request errcode: " + QSTRN(reply->error())) LOG(NETWORK, "Network request errcode: " + QSTRN(reply->error()))
emit httpRequestFinished(this->data); emit httpRequestFinished(this->data);
} }
void QvHttpRequestHelper::onReadyRead() void QvHttpRequestHelper::onReadyRead()
{ {
DEBUG(MODULE_NETWORK, "A request is now ready read") DEBUG(NETWORK, "A request is now ready read")
this->data += reply->readAll(); this->data += reply->readAll();
} }
} }

View File

@ -16,16 +16,14 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#ifndef QVHTTPREQUESTHELPER_H #pragma once
#define QVHTTPREQUESTHELPER_H
#include <QObject> #include <QObject>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include "QvUtils.hpp"
namespace Qv2ray namespace Qv2ray::common
{ {
class QvHttpRequestHelper : public QObject class QvHttpRequestHelper : public QObject
{ {
@ -61,4 +59,4 @@ namespace Qv2ray
}; };
} }
#endif // QVHTTPREQUESTHELPER_H using namespace Qv2ray::common;

View File

@ -0,0 +1,133 @@
#include "LogHighlighter.hpp"
#include "common/QvHelpers.hpp"
#define TO_EOL "(([\\s\\S]*)|([\\d\\D]*)|([\\w\\W]*))$"
namespace Qv2ray::common
{
SyntaxHighlighter::SyntaxHighlighter(bool darkMode, QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
HighlightingRule rule;
keywordFormat.setForeground(darkMode ? Qt::darkMagenta : Qt::magenta);
keywordFormat.setFontWeight(QFont::Bold);
const QString keywordPatterns[] = {
"tcp", "udp"
};
for (const QString &pattern : keywordPatterns) {
rule.pattern = QRegularExpression(pattern);
rule.format = keywordFormat;
highlightingRules.append(rule);
}
if (darkMode) {
ipHostFormat.setForeground(Qt::yellow);
warningFormat.setForeground(QColor(230, 180, 0));
} else {
ipHostFormat.setForeground(Qt::black);
ipHostFormat.setFontWeight(QFont::Bold);
warningFormat.setForeground(Qt::white);
warningFormat.setBackground(QColor(255, 128, 30));
}
//
dateFormat.setFontWeight(QFont::Bold);
dateFormat.setForeground(darkMode ? Qt::cyan : Qt::darkCyan);
rule.pattern = QRegularExpression("\\d\\d\\d\\d/\\d\\d/\\d\\d");
rule.format = dateFormat;
highlightingRules.append(rule);
//
timeFormat.setFontWeight(QFont::Bold);
timeFormat.setForeground(darkMode ? Qt::cyan : Qt::darkCyan);
rule.pattern = QRegularExpression("\\d\\d:\\d\\d:\\d\\d");
rule.format = timeFormat;
highlightingRules.append(rule);
//
debugFormat.setForeground(Qt::darkGray);
rule.pattern = QRegularExpression("\\[[Dd]ebug\\]" TO_EOL);
rule.format = debugFormat;
highlightingRules.append(rule);
//
infoFormat.setForeground(darkMode ? Qt::lightGray : Qt::darkCyan);
rule.pattern = QRegularExpression("\\[[Ii]nfo\\]" TO_EOL);
rule.format = infoFormat;
highlightingRules.append(rule);
//
//
{
// IP IPv6 Host;
rule.pattern = QRegularExpression(REGEX_IPV4_ADDR ":" REGEX_PORT_NUMBER);
rule.pattern.setPatternOptions(QRegularExpression::PatternOption::ExtendedPatternSyntaxOption);
rule.format = ipHostFormat;
highlightingRules.append(rule);
//
rule.pattern = QRegularExpression(REGEX_IPV6_ADDR ":" REGEX_PORT_NUMBER);
rule.pattern.setPatternOptions(QRegularExpression::PatternOption::ExtendedPatternSyntaxOption);
rule.format = ipHostFormat;
highlightingRules.append(rule);
//
rule.pattern = QRegularExpression("([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/|):" REGEX_PORT_NUMBER);
rule.pattern.setPatternOptions(QRegularExpression::PatternOption::ExtendedPatternSyntaxOption);
rule.format = ipHostFormat;
highlightingRules.append(rule);
}
//
//
acceptedFormat.setForeground(Qt::darkGreen);
rule.pattern = QRegularExpression("\\saccepted\\s");
rule.format = acceptedFormat;
highlightingRules.append(rule);
//
rejectedFormat.setFontWeight(QFont::Bold);
rejectedFormat.setBackground(Qt::red);
rejectedFormat.setForeground(Qt::white);
rule.pattern = QRegularExpression("\\srejected\\s" TO_EOL);
rule.format = rejectedFormat;
highlightingRules.append(rule);
//
v2rayComponentFormat.setFontWeight(QFont::Bold);
v2rayComponentFormat.setForeground(darkMode ? Qt::darkGreen : Qt::darkYellow);
rule.pattern = QRegularExpression(" v2ray.com(/(\\w*))*: ");
rule.format = v2rayComponentFormat;
highlightingRules.append(rule);
//
warningFormat.setFontWeight(QFont::Bold);
rule.pattern = QRegularExpression("\\[[Ww]arning\\]" TO_EOL);
rule.format = warningFormat;
highlightingRules.append(rule);
//
failedFormat.setFontWeight(QFont::Bold);
failedFormat.setBackground(Qt::red);
failedFormat.setForeground(Qt::white);
rule.pattern = QRegularExpression("failed");
rule.format = failedFormat;
highlightingRules.append(rule);
//
qvAppLogFormat.setFontWeight(QFont::Bold);
qvAppLogFormat.setForeground(darkMode ? Qt::cyan : Qt::darkCyan);
rule.pattern = QRegularExpression("\\[[A-Z]*\\]:");
rule.format = qvAppLogFormat;
highlightingRules.append(rule);
//
qvAppDebugLogFormat.setFontWeight(QFont::Bold);
qvAppDebugLogFormat.setForeground(darkMode ? Qt::yellow : Qt::darkYellow);
rule.pattern = QRegularExpression("\\[\\[DEBUG\\] - [A-Z]*\\]:");
rule.format = qvAppDebugLogFormat;
highlightingRules.append(rule);
}
void SyntaxHighlighter::highlightBlock(const QString &text)
{
for (const HighlightingRule &rule : qAsConst(highlightingRules)) {
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext()) {
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
setCurrentBlockState(0);
}
}

View File

@ -48,54 +48,46 @@
** **
****************************************************************************/ ****************************************************************************/
#ifndef HIGHLIGHTER_H #pragma once
#define HIGHLIGHTER_H
#include <QSyntaxHighlighter> #include <QSyntaxHighlighter>
#include <QTextCharFormat> #include <QTextCharFormat>
#include <QRegularExpression> #include <QRegularExpression>
#include <QTextDocument> #include <QTextDocument>
#include "QvUtils.hpp"
namespace Qv2ray namespace Qv2ray::common
{ {
namespace Components class SyntaxHighlighter : public QSyntaxHighlighter
{ {
class SyntaxHighlighter : public QSyntaxHighlighter Q_OBJECT
{
Q_OBJECT
public: public:
explicit SyntaxHighlighter(bool darkMode, QTextDocument *parent = nullptr); explicit SyntaxHighlighter(bool darkMode, QTextDocument *parent = nullptr);
protected: protected:
void highlightBlock(const QString &text) override; void highlightBlock(const QString &text) override;
private: private:
struct HighlightingRule { struct HighlightingRule {
QRegularExpression pattern; QRegularExpression pattern;
QTextCharFormat format; QTextCharFormat format;
}; };
QVector<HighlightingRule> highlightingRules; QVector<HighlightingRule> highlightingRules;
QTextCharFormat keywordFormat; QTextCharFormat keywordFormat;
QTextCharFormat dateFormat; QTextCharFormat dateFormat;
QTextCharFormat acceptedFormat; QTextCharFormat acceptedFormat;
QTextCharFormat rejectedFormat; QTextCharFormat rejectedFormat;
QTextCharFormat failedFormat; QTextCharFormat failedFormat;
QTextCharFormat warningFormat; QTextCharFormat warningFormat;
QTextCharFormat infoFormat; QTextCharFormat infoFormat;
QTextCharFormat debugFormat; QTextCharFormat debugFormat;
QTextCharFormat timeFormat; QTextCharFormat timeFormat;
QTextCharFormat ipHostFormat; QTextCharFormat ipHostFormat;
QTextCharFormat v2rayComponentFormat; QTextCharFormat v2rayComponentFormat;
// //
QTextCharFormat qvAppLogFormat; QTextCharFormat qvAppLogFormat;
QTextCharFormat qvAppDebugLogFormat; QTextCharFormat qvAppDebugLogFormat;
}; };
}
} }
using namespace Qv2ray::Components; using namespace Qv2ray::common;
#endif // HIGHLIGHTER_H

View File

@ -22,8 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
#ifndef QJSONMODEL_H #pragma once
#define QJSONMODEL_H
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QJsonDocument> #include <QJsonDocument>
@ -100,5 +99,3 @@ class QJsonModel : public QAbstractItemModel
}; };
#endif // QJSONMODEL_H

212
src/common/QvHelpers.cpp Normal file
View File

@ -0,0 +1,212 @@
#include "common/QvHelpers.hpp"
#include <QQueue>
namespace Qv2ray::common
{
const QString GenerateRandomString(int len)
{
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString randomString;
for (int i = 0; i < len; ++i) {
uint rand = QRandomGenerator::system()->generate();
uint max = static_cast<uint>(possibleCharacters.length());
QChar nextChar = possibleCharacters[rand % max];
randomString.append(nextChar);
}
return randomString;
}
QString StringFromFile(const QString &filePath)
{
QFile f(filePath);
return StringFromFile(&f);
}
QString StringFromFile(QFile *source)
{
source->open(QFile::ReadOnly);
QTextStream stream(source);
QString str = stream.readAll();
source->close();
return str;
}
bool StringToFile(const QString *text, QFile *targetFile)
{
bool override = targetFile->exists();
targetFile->open(QFile::WriteOnly);
QTextStream stream(targetFile);
stream << *text << endl;
stream.flush();
targetFile->close();
return override;
}
QJsonObject JSONFromFile(QFile *sourceFile)
{
QString json = StringFromFile(sourceFile);
return JsonFromString(json);
}
QString JsonToString(QJsonObject json, QJsonDocument::JsonFormat format)
{
QJsonDocument doc;
doc.setObject(json);
return doc.toJson(format);
}
QString JsonToString(QJsonArray array, QJsonDocument::JsonFormat format)
{
QJsonDocument doc;
doc.setArray(array);
return doc.toJson(format);
}
QString VerifyJsonString(const QString &source)
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error);
Q_UNUSED(doc)
if (error.error == QJsonParseError::NoError) {
return "";
} else {
LOG(UI, "WARNING: Json parse returns: " + error.errorString())
return error.errorString();
}
}
QJsonObject JsonFromString(QString string)
{
QJsonDocument doc = QJsonDocument::fromJson(string.toUtf8());
return doc.object();
}
QString Base64Encode(QString string)
{
QByteArray ba = string.toUtf8();
return ba.toBase64();
}
QString Base64Decode(QString string)
{
QByteArray ba = string.toUtf8();
return QString(QByteArray::fromBase64(ba));
}
QStringList SplitLines(const QString &_string)
{
return _string.split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
}
list<string> SplitLines_std(const QString &_string)
{
list<string> list;
for (auto line : _string.split(QRegExp("[\r\n]"), QString::SkipEmptyParts)) {
list.push_back(line.toStdString());
}
return list;
}
QStringList GetFileList(QDir dir)
{
return dir.entryList(QStringList() << "*" << "*.*", QDir::Hidden | QDir::Files);
}
bool FileExistsIn(QDir dir, QString fileName)
{
return GetFileList(dir).contains(fileName);
}
void QvMessageBoxWarn(QWidget *parent, QString title, QString text)
{
QMessageBox::warning(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0);
}
void QvMessageBoxInfo(QWidget *parent, QString title, QString text)
{
QMessageBox::information(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0);
}
QMessageBox::StandardButton QvMessageBoxAsk(QWidget *parent, QString title, QString text, QMessageBox::StandardButton extraButtons)
{
return QMessageBox::question(parent, title, text, QMessageBox::Yes | QMessageBox::No | extraButtons);
}
QString FormatBytes(long long bytes)
{
char str[64];
const char *sizes[5] = { "B", "KB", "MB", "GB", "TB" };
int i;
double dblByte = bytes;
for (i = 0; i < 5 && bytes >= 1024; i++, bytes /= 1024)
dblByte = bytes / 1024.0;
sprintf(str, "%.2f", dblByte);
return strcat(strcat(str, " "), sizes[i]);
}
bool IsValidFileName(const QString &fileName)
{
QString name = fileName;
return name == RemoveInvalidFileName(fileName);
}
QString RemoveInvalidFileName(const QString &fileName)
{
std::string _name = fileName.toStdString();
std::replace_if(_name.begin(), _name.end(), [](char c) {
return std::string::npos != string(R"("/\?%&^*;:|><)").find(c);
}, '_');
return QString::fromStdString(_name);
}
/// This returns a file name without extensions.
void DeducePossibleFileName(const QString &baseDir, QString *fileName, const QString &extension)
{
int i = 1;
if (!QDir(baseDir).exists()) {
QDir(baseDir).mkpath(baseDir);
LOG(FILEIO, "Making path: " + baseDir)
}
while (true) {
if (!QFile(baseDir + "/" + fileName + "_" + QSTRN(i) + extension).exists()) {
*fileName = *fileName + "_" + QSTRN(i);
return;
} else {
DEBUG(FILEIO, "File with name: " + *fileName + "_" + QSTRN(i) + extension + " already exists")
}
i++;
}
}
QStringList ConvertQStringList(const QList<string> &stdListString)
{
QStringList listQt;
listQt.reserve(stdListString.size());
for (const std::string &s : stdListString) {
listQt.append(QString::fromStdString(s));
}
return listQt;
}
std::list<string> ConvertStdStringList(const QStringList &qStringList)
{
std::list<string> stdList;
for (auto &s : qStringList) {
stdList.push_back(s.toStdString());
}
return stdList;
}
}

126
src/common/QvHelpers.hpp Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include "base/Qv2rayBase.hpp"
#include <QMessageBox>
#define REGEX_IPV6_ADDR R"(\[\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*\])"
#define REGEX_IPV4_ADDR R"((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5]))"
#define REGEX_PORT_NUMBER R"(([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])*)"
namespace Qv2ray::common
{
QStringList GetFileList(QDir dir);
QString Base64Encode(QString string);
QString Base64Decode(QString string);
QStringList SplitLines(const QString &str);
list<string> SplitLines_std(const QString &_string);
bool FileExistsIn(QDir dir, QString fileName);
const QString GenerateRandomString(int len = 12);
//
void QvMessageBoxWarn(QWidget *parent, QString title, QString text);
void QvMessageBoxInfo(QWidget *parent, QString title, QString text);
QMessageBox::StandardButton QvMessageBoxAsk(QWidget *parent, QString title, QString text, QMessageBox::StandardButton extraButtons = QMessageBox::NoButton);
//
QString StringFromFile(const QString &filePath);
QString StringFromFile(QFile *source);
bool StringToFile(const QString *text, QFile *target);
//
QJsonObject JsonFromString(QString string);
QString JsonToString(QJsonObject json, QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented);
QString JsonToString(QJsonArray array, QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat::Indented);
QString VerifyJsonString(const QString &source);
//
QString FormatBytes(long long bytes);
void DeducePossibleFileName(const QString &baseDir, QString *fileName, const QString &extension);
QStringList ConvertQStringList(const QList<string> &stdListString);
std::list<string> ConvertStdStringList(const QStringList &qStringList);
// This function cannot be marked as inline.
QString RemoveInvalidFileName(const QString &fileName);
bool IsValidFileName(const QString &fileName);
//
template <typename TYPE>
QString StructToJsonString(const TYPE t)
{
return QString::fromStdString(x2struct::X::tojson(t, "", 4, ' '));
}
//
template <typename TYPE>
TYPE StructFromJsonString(const QString &str)
{
TYPE v;
x2struct::X::loadjson(str.toStdString(), v, false);
return v;
}
// Misc
template<typename T>
QJsonObject GetRootObject(const T &t)
{
auto json = StructToJsonString(t);
return JsonFromString(json);
}
inline bool IsIPv4Address(const QString &addr)
{
return QRegularExpression(REGEX_IPV4_ADDR "$").match(addr).hasMatch();
}
inline bool IsIPv6Address(const QString &addr)
{
return QRegularExpression(REGEX_IPV6_ADDR "$").match(addr).hasMatch();
}
inline bool IsValidIPAddress(const QString &addr)
{
return IsIPv4Address(addr) || IsIPv6Address(addr);
}
/*
* Generic function to find if an element of any type exists in list
*/
template<typename T>
bool contains(std::list<T> &listOfElements, const T &element)
{
// Find the iterator if element in list
auto it = std::find(listOfElements.begin(), listOfElements.end(), element);
//return if iterator points to end or not. It points to end then it means element
// does not exists in list
return it != listOfElements.end();
}
inline QString timeToString(const time_t &t)
{
auto _tm = std::localtime(&t);
char MY_TIME[128];
// using strftime to display time
strftime(MY_TIME, sizeof(MY_TIME), "%x - %I:%M%p", _tm);
return QString(MY_TIME);
}
template<typename myMap>
std::vector<typename myMap::key_type> Keys(const myMap &m)
{
std::vector<typename myMap::key_type> r;
r.reserve(m.size());
for (const auto &kvp : m) {
r.push_back(kvp.first);
}
return r;
}
template<typename myMap>
std::vector<typename myMap::mapped_type> Values(const myMap &m)
{
std::vector<typename myMap::mapped_type> r;
r.reserve(m.size());
for (const auto &kvp : m) {
r.push_back(kvp.second);
}
return r;
}
}
using namespace Qv2ray::common;

View File

@ -0,0 +1,21 @@
#pragma once
#include <QString>
#include <QTranslator>
#include <memory>
namespace Qv2ray::common
{
class QvTranslator
{
public:
QvTranslator(const QString &lang)
{
QTranslator *translator = new QTranslator();
translator->load(lang + ".qm", ":/translations/");
this->pTranslator.reset(translator);
}
public:
std::unique_ptr<QTranslator> pTranslator;
};
} // namespace Qv2ray::common

View File

@ -1,3 +0,0 @@
#include "QvComponentsHandler.hpp"

View File

@ -1,14 +0,0 @@
#ifndef QVCOMPONENTSHANDLER_H
#define QVCOMPONENTSHANDLER_H
#include "QvUtils.hpp"
namespace Qv2ray
{
namespace Components
{
// TODO
}
}
using namespace Qv2ray::Components;
#endif // QVCOMPONENTSHANDLER_H

View File

@ -1,68 +0,0 @@
#include "QvCommandLineArgs.hpp"
#include "Qv2rayBase.hpp"
#include <QCommandLineParser>
namespace Qv2ray
{
namespace CommandArgOperations
{
// Instantiation
QvStartupOptions StartupOption = QvStartupOptions{};
QvCommandArgParser::QvCommandArgParser() : QObject(),
noAPIOption("noAPI", QObject::tr("Disable gRPC API subsystems.")),
runAsRootOption("I-just-wanna-run-with-root", QObject::tr("Explicitly run Qv2ray as root.")),
debugOption("debug", QObject::tr("Enable Debug Output")),
withToolbarOption("withToolbarPlugin", QObject::tr("Enable Qv2ray network toolbar plugin")),
//
helpOption("FAKE"), versionOption("FAKE")
{
parser.setApplicationDescription(QObject::tr("Qv2ray - A cross-platform Qt frontend for V2ray."));
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
//
parser.addOption(noAPIOption);
parser.addOption(runAsRootOption);
parser.addOption(debugOption);
parser.addOption(withToolbarOption);
helpOption = parser.addHelpOption();
versionOption = parser.addVersionOption();
}
CommandLineParseResult QvCommandArgParser::ParseCommandLine(QString *errorMessage)
{
if (!parser.parse(QCoreApplication::arguments())) {
*errorMessage = parser.errorText();
return CommandLineError;
}
if (parser.isSet(versionOption))
return CommandLineVersionRequested;
if (parser.isSet(helpOption))
return CommandLineHelpRequested;
if (parser.isSet(noAPIOption)) {
DEBUG(MODULE_INIT, "noAPIOption is set.")
StartupOption.noAPI = true;
}
if (parser.isSet(runAsRootOption)) {
DEBUG(MODULE_INIT, "runAsRootOption is set.")
StartupOption.forceRunAsRootUser = true;
}
if (parser.isSet(debugOption)) {
DEBUG(MODULE_INIT, "debugOption is set.")
StartupOption.debugLog = true;
}
if (parser.isSet(withToolbarOption)) {
DEBUG(MODULE_INIT, "withToolbarOption is set.")
StartupOption.enableToolbarPlguin = true;
}
return CommandLineOk;
}
}
}

View File

@ -1,53 +0,0 @@
#ifndef QVCOMMANDLINEARGS_HPP
#define QVCOMMANDLINEARGS_HPP
#include "Qv2rayBase.hpp"
namespace Qv2ray
{
namespace CommandArgOperations
{
struct QvStartupOptions {
/// No API subsystem
bool noAPI;
/// Explicitly run as root user.
bool forceRunAsRootUser;
/// Enable Debug Log.
bool debugLog;
/// Enable Network toolbar plugin.
bool enableToolbarPlguin;
};
enum CommandLineParseResult {
CommandLineOk,
CommandLineError,
CommandLineVersionRequested,
CommandLineHelpRequested
};
//
extern QvStartupOptions StartupOption;
class QvCommandArgParser : public QObject
{
Q_OBJECT
public:
QvCommandArgParser();
CommandLineParseResult ParseCommandLine(QString *errorMessage);
const QCommandLineParser *Parser()
{
return &parser;
}
private:
QCommandLineParser parser;
QCommandLineOption noAPIOption;
QCommandLineOption runAsRootOption;
QCommandLineOption debugOption;
QCommandLineOption withToolbarOption;
QCommandLineOption helpOption;
QCommandLineOption versionOption;
};
}
}
using namespace Qv2ray::CommandArgOperations;
#endif

View File

@ -1,136 +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 "QvUtils.hpp"
namespace Qv2ray
{
namespace Utils
{
// 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);
}
}
}

View File

@ -1,361 +0,0 @@
#include <QObject>
#include <QWidget>
#include <QDesktopServices>
#include "QvKernelInteractions.hpp"
#include "QvCoreConfigOperations.hpp"
#include "QvCore/QvCommandLineArgs.hpp"
#ifdef WITH_LIB_GRPCPP
using namespace v2ray::core::app::stats::command;
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
#else
#include "libs/libqvb/build/libqvb.h"
#endif
// Check 10 times before telling user that API has failed.
#define QV2RAY_API_CALL_FAILEDCHECK_THRESHOLD 10
namespace Qv2ray
{
namespace QvKernelInterations
{
bool V2rayKernelInstance::ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message)
{
QFile coreFile(vCorePath);
if (!coreFile.exists()) {
DEBUG(MODULE_VCORE, "V2ray core file cannot be found.")
*message = tr("V2ray core executable not found.");
return false;
}
// Use open() here to prevent `executing` a folder, which may have the same name as the V2ray core.
if (!coreFile.open(QFile::ReadOnly)) {
DEBUG(MODULE_VCORE, "V2ray core file cannot be opened, possibly be a folder?")
*message = tr("V2ray core file cannot be opened, please ensure there's a file instead of a folder.");
return false;
}
coreFile.close();
//
// Check file existance.
// From: https://www.v2fly.org/chapter_02/env.html#asset-location
//
bool hasGeoIP = FileExistsIn(QDir(vAssetsPath), "geoip.dat");
bool hasGeoSite = FileExistsIn(QDir(vAssetsPath), "geosite.dat");
if (!hasGeoIP && !hasGeoSite) {
DEBUG(MODULE_VCORE, "V2ray assets path contains none of those two files.")
*message = tr("V2ray assets path is not valid.");
return false;
}
if (!hasGeoIP) {
DEBUG(MODULE_VCORE, "No geoip.dat in assets path, aborting.")
*message = tr("No geoip.dat in assets path.");
return false;
}
if (!hasGeoSite) {
DEBUG(MODULE_VCORE, "No geosite.dat in assets path, aborting.")
*message = tr("No geosite.dat in assets path.");
return false;
}
// Check if V2ray core returns a version number correctly.
QProcess proc;
#ifdef Q_OS_WIN32
// nativeArguments are required for Windows platform, without a reason...
proc.setProcessChannelMode(QProcess::MergedChannels);
proc.setProgram(vCorePath);
proc.setNativeArguments("--version");
proc.start();
#else
proc.start(vCorePath + " --version");
#endif
proc.waitForStarted();
proc.waitForFinished();
auto exitCode = proc.exitCode();
if (exitCode != 0) {
DEBUG(MODULE_VCORE, "VCore failed with an exit code: " + QSTRN(exitCode))
*message = tr("V2ray core failed with an exit code: ") + QSTRN(exitCode);
return false;
}
QString output = proc.readAllStandardOutput();
LOG(MODULE_VCORE, "V2ray output: " + SplitLines(output).join(";"))
if (SplitLines(output).isEmpty()) {
*message = tr("V2ray core returns empty string.");
return false;
}
*message = SplitLines(output).first();
return true;
}
bool V2rayKernelInstance::ValidateConfig(const QString &path)
{
auto conf = GetGlobalConfig();
QString v2rayCheckResult;
if (ValidateKernel(conf.v2CorePath, conf.v2AssetsPath, &v2rayCheckResult)) {
DEBUG(MODULE_VCORE, "V2ray version: " + v2rayCheckResult)
// Append assets location env.
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("V2RAY_LOCATION_ASSET", conf.v2AssetsPath);
//
QProcess process;
process.setProcessEnvironment(env);
DEBUG(MODULE_VCORE, "Starting V2ray core with test options")
process.start(conf.v2CorePath, QStringList() << "-test" << "-config" << path, QIODevice::ReadWrite | QIODevice::Text);
process.waitForFinished();
if (process.exitCode() != 0) {
QString output = QString(process.readAllStandardOutput());
QvMessageBoxWarn(nullptr, tr("Configuration Error"), output.mid(output.indexOf("anti-censorship.") + 17));
return false;
} else {
DEBUG(MODULE_VCORE, "Config file check passed.")
return true;
}
} else {
QvMessageBoxWarn(nullptr, tr("Cannot start V2ray"),
tr("V2ray core settings is incorrect.") + NEWLINE + NEWLINE +
tr("The error is: ") + NEWLINE + v2rayCheckResult);
return false;
}
}
V2rayKernelInstance::V2rayKernelInstance()
{
vProcess = new QProcess();
connect(vProcess, &QProcess::readyReadStandardOutput, this, [this]() {
emit onProcessOutputReadyRead(vProcess->readAllStandardOutput().trimmed());
});
connect(vProcess, &QProcess::stateChanged, [this](QProcess::ProcessState state) {
DEBUG(MODULE_VCORE, "V2ray kernel process status changed: " + QVariant::fromValue(state).toString())
// If V2ray crashed AFTER we start it.
if (KernelStarted && state == QProcess::NotRunning) {
LOG(MODULE_VCORE, "V2ray kernel crashed.")
emit onProcessErrored();
}
});
KernelStarted = false;
}
bool V2rayKernelInstance::StartConnection(CONFIGROOT root)
{
if (KernelStarted) {
LOG(MODULE_VCORE, "Status is invalid, expect STOPPED when calling StartConnection")
return false;
}
inboundTags.clear();
for (auto item : root["inbounds"].toArray()) {
auto tag = item.toObject()["tag"].toString("");
if (tag.isEmpty() || tag == API_TAG_INBOUND) {
// Ignore API tag and empty tags.
continue;
}
inboundTags.append(tag);
}
DEBUG(MODULE_VCORE, "Found inbound tags: " + inboundTags.join(";"))
// Write the final configuration to the disk.
QString json = JsonToString(root);
StringToFile(&json, new QFile(QV2RAY_GENERATED_FILE_PATH));
//
auto filePath = QV2RAY_GENERATED_FILE_PATH;
if (ValidateConfig(filePath)) {
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("V2RAY_LOCATION_ASSET", GetGlobalConfig().v2AssetsPath);
vProcess->setProcessEnvironment(env);
vProcess->start(GetGlobalConfig().v2CorePath, QStringList() << "-config" << filePath, QIODevice::ReadWrite | QIODevice::Text);
vProcess->waitForStarted();
DEBUG(MODULE_VCORE, "V2ray core started.")
KernelStarted = true;
auto conf = GetGlobalConfig();
if (StartupOption.noAPI) {
LOG(MODULE_VCORE, "API has been disabled by the command line argument \"-noAPI\"")
} else if (!conf.apiConfig.enableAPI) {
LOG(MODULE_VCORE, "API has been disabled by the global config option")
} else if (inboundTags.isEmpty()) {
LOG(MODULE_VCORE, "API is disabled since no inbound tags configured. This is probably caused by a bad complex config.")
} else {
// Config API
apiFailedCounter = 0;
this->apiPort = conf.apiConfig.statsPort;
auto channelAddress = "127.0.0.1:" + QString::number(apiPort);
#ifdef WITH_LIB_GRPCPP
Channel = grpc::CreateChannel(channelAddress.toStdString(), grpc::InsecureChannelCredentials());
StatsService service;
Stub = service.NewStub(Channel);
#else
auto str = Dial(const_cast<char *>(channelAddress.toStdString().c_str()), 10000);
LOG(MODULE_VCORE, str)
#endif
apiTimerId = startTimer(1000);
DEBUG(MODULE_VCORE, "API Worker started.")
}
return true;
} else {
KernelStarted = false;
return false;
}
}
void V2rayKernelInstance::timerEvent(QTimerEvent *event)
{
QObject::timerEvent(event);
if (event->timerId() == apiTimerId) {
// Call API
for (auto tag : inboundTags) {
// Upload
auto valup = CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>uplink");
auto dataup = valup - transferData[tag + "_up"];
transferData[tag + "_up"] = valup;
transferSpeed[tag + "_up"] = dataup;
// Download
auto valdown = CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>downlink");
auto datadown = valdown - transferData[tag + "_down"];
transferData[tag + "_down"] = valdown;
transferSpeed[tag + "_down"] = datadown;
}
}
}
void V2rayKernelInstance::StopConnection()
{
KernelStarted = false;
vProcess->close();
killTimer(apiTimerId);
apiFailedCounter = 0;
transferData.clear();
transferSpeed.clear();
}
V2rayKernelInstance::~V2rayKernelInstance()
{
if (KernelStarted) {
StopConnection();
}
delete vProcess;
}
long V2rayKernelInstance::CallStatsAPIByName(QString name)
{
if (!KernelStarted) {
LOG(MODULE_VCORE, "Invalid connection status when calling API")
return 0;
}
if (apiFailedCounter == QV2RAY_API_CALL_FAILEDCHECK_THRESHOLD) {
LOG(MODULE_VCORE, "API call failure threshold reached, cancelling further API aclls.")
QvMessageBoxWarn(nullptr, tr("API Call Failed"), tr("Failed to get statistics data, please check if V2ray is running properly"));
transferData.clear();
transferSpeed.clear();
apiFailedCounter++;
return 0;
} else if (apiFailedCounter > QV2RAY_API_CALL_FAILEDCHECK_THRESHOLD) {
return 0;
}
#ifdef WITH_LIB_GRPCPP
GetStatsRequest request;
request.set_name(name.toStdString());
request.set_reset(false);
GetStatsResponse response;
ClientContext context;
Status status = Stub->GetStats(&context, request, &response);
if (!status.ok()) {
LOG(MODULE_VCORE, "API call returns: " + QSTRN(status.error_code()) + " (" + QString::fromStdString(status.error_message()) + ")")
apiFailedCounter++;
}
auto data = response.stat().value();
#else
auto data = GetStats(const_cast<char *>(name.toStdString().c_str()), 1000);
#endif
if (data < 0) {
LOG(MODULE_VCORE, "API call returns: " + QSTRN(data))
apiFailedCounter++;
return 0;
}
return data;
}
// ------------------------------------------------------------- API FUNCTIONS --------------------------
long V2rayKernelInstance::getTagSpeedUp(const QString &tag)
{
return transferSpeed[tag + "_up"];
}
long V2rayKernelInstance::getTagSpeedDown(const QString &tag)
{
return transferSpeed[tag + "_down"];
}
long V2rayKernelInstance::getTagDataUp(const QString &tag)
{
return transferData[tag + "_up"];
}
long V2rayKernelInstance::getTagDataDown(const QString &tag)
{
return transferData[tag + "_down"];
}
long V2rayKernelInstance::getAllDataUp()
{
long val = 0;
for (auto tag : inboundTags) {
val += transferData[tag + "_up"];
}
return val;
}
long V2rayKernelInstance::getAllDataDown()
{
long val = 0;
for (auto tag : inboundTags) {
val += transferData[tag + "_down"];
}
return val;
}
long V2rayKernelInstance::getAllSpeedUp()
{
long val = 0;
for (auto tag : inboundTags) {
val += transferSpeed[tag + "_up"];
}
return val;
}
long V2rayKernelInstance::getAllSpeedDown()
{
long val = 0;
for (auto tag : inboundTags) {
val += transferSpeed[tag + "_down"];
}
return val;
}
}
}

View File

@ -1,66 +0,0 @@
#ifndef VINTERACT_H
#define VINTERACT_H
#include <QProcess>
#include "QvUtils.hpp"
#ifdef WITH_LIB_GRPCPP
#include <grpc++/grpc++.h>
#include "v2ray_api_commands.pb.h"
#include "v2ray_api_commands.grpc.pb.h"
#endif
namespace Qv2ray
{
namespace QvKernelInterations
{
class V2rayKernelInstance : public QObject
{
Q_OBJECT
public:
explicit V2rayKernelInstance();
~V2rayKernelInstance() override;
//
// Speed
long getTagSpeedUp(const QString &tag);
long getTagSpeedDown(const QString &tag);
long getTagDataUp(const QString &tag);
long getTagDataDown(const QString &tag);
long getAllDataUp();
long getAllDataDown();
long getAllSpeedUp();
long getAllSpeedDown();
//
bool StartConnection(CONFIGROOT root);
void StopConnection();
bool KernelStarted = false;
//
static bool ValidateConfig(const QString &path);
static bool ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message);
signals:
void onProcessErrored();
void onProcessOutputReadyRead(QString);
private:
void timerEvent(QTimerEvent *event) override;
QStringList inboundTags;
int apiTimerId = -1;
int apiPort;
//
int apiFailedCounter;
long CallStatsAPIByName(QString name);
QProcess *vProcess;
//
QMap<QString, long> transferData;
QMap<QString, long> transferSpeed;
//
#ifdef WITH_LIB_GRPCPP
std::shared_ptr<::grpc::Channel> Channel;
std::unique_ptr<::v2ray::core::app::stats::command::StatsService::Stub> Stub;
#endif
};
}
}
using namespace Qv2ray::QvKernelInterations;
#endif // VINTERACT_H

View File

@ -1,189 +0,0 @@
#include "QvLaunchAtLoginConfigurator.hpp"
#include <QSettings>
#include <QApplication>
#include <QDir>
#include <QStandardPaths>
#include <QTextStream>
// macOS headers (possibly OBJ-c)
#if defined(Q_OS_MAC)
#include <CoreServices/CoreServices.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
namespace Qv2ray
{
namespace Components
{
//
// launchatlogin.cpp
// ShadowClash
//
// Created by TheWanderingCoel on 2018/6/12.
// Copyright © 2019 Coel Wu. All rights reserved.
//
QString getUserAutostartDir_private()
{
QString config = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
config += QLatin1String("/autostart/");
return config;
}
bool GetLaunchAtLoginStatus()
{
#ifdef Q_OS_WIN
QString appName = QApplication::applicationName();
QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
return reg.contains(appName);
}
#elif defined Q_OS_MAC
// From https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp
// this is quite some duplicate code with setLaunchOnStartup, at some point we should fix this FIXME.
bool returnValue = false;
QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath();
CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8);
CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true);
LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0);
if (loginItems)
{
// We need to iterate over the items and check which one is "ours".
UInt32 seedValue;
CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);
CFStringRef appUrlRefString = CFURLGetString(urlRef); // no need for release
for (int i = 0; i < CFArrayGetCount(itemsArray); i++) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
CFURLRef itemUrlRef = NULL;
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
returnValue = true;
}
CFRelease(itemUrlRef);
}
}
CFRelease(itemsArray);
}
CFRelease(loginItems);
CFRelease(folderCFStr);
CFRelease(urlRef);
return returnValue;
}
#elif defined Q_OS_LINUX
QString appName = QApplication::applicationName();
QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop");
return QFile::exists(desktopFileLocation);
}
#endif
void SetLaunchAtLoginStatus(bool enable)
{
#ifdef Q_OS_WIN
QString appName = QApplication::applicationName();
QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
if (enable) {
QString strAppPath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
reg.setValue(appName, strAppPath);
} else {
reg.remove(appName);
}
}
#elif defined Q_OS_MAC
// From https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp
QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath();
CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8);
CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true);
LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0);
if (loginItems && enable)
{
//Insert an item to the list.
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems,
kLSSharedFileListItemLast, 0, 0,
urlRef, 0, 0);
if (item)
CFRelease(item);
CFRelease(loginItems);
} else if (loginItems && !enable)
{
// We need to iterate over the items and check which one is "ours".
UInt32 seedValue;
CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);
CFStringRef appUrlRefString = CFURLGetString(urlRef);
for (int i = 0; i < CFArrayGetCount(itemsArray); i++) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
CFURLRef itemUrlRef = NULL;
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
LSSharedFileListItemRemove(loginItems, item); // remove it!
}
CFRelease(itemUrlRef);
}
}
CFRelease(itemsArray);
CFRelease(loginItems);
}
CFRelease(folderCFStr);
CFRelease(urlRef);
}
#elif defined Q_OS_LINUX
// From https://github.com/nextcloud/desktop/blob/master/src/common/utility_unix.cpp
QString appName = QApplication::applicationName();
QString userAutoStartPath = getUserAutostartDir_private();
QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
if (enable)
{
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
// qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
return;
}
QFile iniFile(desktopFileLocation);
if (!iniFile.open(QIODevice::WriteOnly)) {
// qCWarning(lcUtility) << "Could not write auto start entry" << desktopFileLocation;
return;
}
QTextStream ts(&iniFile);
ts.setCodec("UTF-8");
ts << QLatin1String("[Desktop Entry]") << endl
<< QLatin1String("Name=") << QApplication::applicationName() << endl
<< QLatin1String("GenericName=") << QLatin1String("File Synchronizer") << endl
<< QLatin1String("Exec=") << QCoreApplication::applicationFilePath() << endl
<< QLatin1String("Terminal=") << "false" << endl
<< QLatin1String("Icon=") << "qv2ray" << endl // always use lowercase for icons
<< QLatin1String("Categories=") << "Network" << endl
<< QLatin1String("Type=") << "Application" << endl
<< QLatin1String("StartupNotify=") << "false" << endl
<< QLatin1String("X-GNOME-Autostart-enabled=") << "true" << endl;
} else
{
if (!QFile::remove(desktopFileLocation)) {
// qCWarning(lcUtility) << "Could not remove autostart desktop file";
}
}
}
#endif
}
}

View File

@ -1,17 +0,0 @@
#ifndef QVLAUNCH_AT_LOGINCONFIGURATOR_H
#define QVLAUNCH_AT_LOGINCONFIGURATOR_H
#include "QvUtils.hpp"
namespace Qv2ray
{
namespace Components
{
bool GetLaunchAtLoginStatus();
void SetLaunchAtLoginStatus(bool enable);
}
}
using namespace Qv2ray::Components;
#endif // QVLAUNCH_AT_LOGINCONFIGURATOR_H

View File

@ -1,136 +0,0 @@
#include "QvLogHighlighter.hpp"
#define REGEX_PORT_NUMBER "([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])*"
#define TO_EOL "(([\\s\\S]*)|([\\d\\D]*)|([\\w\\W]*))$"
namespace Qv2ray
{
namespace Components
{
SyntaxHighlighter::SyntaxHighlighter(bool darkMode, QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
HighlightingRule rule;
keywordFormat.setForeground(darkMode ? Qt::darkMagenta : Qt::magenta);
keywordFormat.setFontWeight(QFont::Bold);
const QString keywordPatterns[] = {
"tcp", "udp"
};
for (const QString &pattern : keywordPatterns) {
rule.pattern = QRegularExpression(pattern);
rule.format = keywordFormat;
highlightingRules.append(rule);
}
if (darkMode) {
ipHostFormat.setForeground(Qt::yellow);
warningFormat.setForeground(QColor(230, 180, 0));
} else {
ipHostFormat.setForeground(Qt::black);
ipHostFormat.setFontWeight(QFont::Bold);
warningFormat.setForeground(Qt::white);
warningFormat.setBackground(QColor(255, 128, 30));
}
//
dateFormat.setFontWeight(QFont::Bold);
dateFormat.setForeground(darkMode ? Qt::cyan : Qt::darkCyan);
rule.pattern = QRegularExpression("\\d\\d\\d\\d/\\d\\d/\\d\\d");
rule.format = dateFormat;
highlightingRules.append(rule);
//
timeFormat.setFontWeight(QFont::Bold);
timeFormat.setForeground(darkMode ? Qt::cyan : Qt::darkCyan);
rule.pattern = QRegularExpression("\\d\\d:\\d\\d:\\d\\d");
rule.format = timeFormat;
highlightingRules.append(rule);
//
debugFormat.setForeground(Qt::darkGray);
rule.pattern = QRegularExpression("\\[[Dd]ebug\\]" TO_EOL);
rule.format = debugFormat;
highlightingRules.append(rule);
//
infoFormat.setForeground(darkMode ? Qt::lightGray : Qt::darkCyan);
rule.pattern = QRegularExpression("\\[[Ii]nfo\\]" TO_EOL);
rule.format = infoFormat;
highlightingRules.append(rule);
//
//
{
// IP IPv6 Host;
rule.pattern = QRegularExpression("(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\:" REGEX_PORT_NUMBER);
rule.pattern.setPatternOptions(QRegularExpression::PatternOption::ExtendedPatternSyntaxOption);
rule.format = ipHostFormat;
highlightingRules.append(rule);
//
rule.pattern = QRegularExpression(REGEX_IPV6_ADDR ":" REGEX_PORT_NUMBER);
rule.pattern.setPatternOptions(QRegularExpression::PatternOption::ExtendedPatternSyntaxOption);
rule.format = ipHostFormat;
highlightingRules.append(rule);
//
rule.pattern = QRegularExpression("([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}(/|):" REGEX_PORT_NUMBER);
rule.pattern.setPatternOptions(QRegularExpression::PatternOption::ExtendedPatternSyntaxOption);
rule.format = ipHostFormat;
highlightingRules.append(rule);
}
//
//
acceptedFormat.setForeground(Qt::darkGreen);
rule.pattern = QRegularExpression("\\saccepted\\s");
rule.format = acceptedFormat;
highlightingRules.append(rule);
//
rejectedFormat.setFontWeight(QFont::Bold);
rejectedFormat.setBackground(Qt::red);
rejectedFormat.setForeground(Qt::white);
rule.pattern = QRegularExpression("\\srejected\\s" TO_EOL);
rule.format = rejectedFormat;
highlightingRules.append(rule);
//
v2rayComponentFormat.setFontWeight(QFont::Bold);
v2rayComponentFormat.setForeground(darkMode ? Qt::darkGreen : Qt::darkYellow);
rule.pattern = QRegularExpression(" v2ray.com(/(\\w*))*: ");
rule.format = v2rayComponentFormat;
highlightingRules.append(rule);
//
warningFormat.setFontWeight(QFont::Bold);
rule.pattern = QRegularExpression("\\[[Ww]arning\\]" TO_EOL);
rule.format = warningFormat;
highlightingRules.append(rule);
//
failedFormat.setFontWeight(QFont::Bold);
failedFormat.setBackground(Qt::red);
failedFormat.setForeground(Qt::white);
rule.pattern = QRegularExpression("failed");
rule.format = failedFormat;
highlightingRules.append(rule);
//
qvAppLogFormat.setFontWeight(QFont::Bold);
qvAppLogFormat.setForeground(darkMode ? Qt::cyan : Qt::darkCyan);
rule.pattern = QRegularExpression("\\[[A-Z]*\\]:");
rule.format = qvAppLogFormat;
highlightingRules.append(rule);
//
qvAppDebugLogFormat.setFontWeight(QFont::Bold);
qvAppDebugLogFormat.setForeground(darkMode ? Qt::yellow : Qt::darkYellow);
rule.pattern = QRegularExpression("\\[\\[DEBUG\\] - [A-Z]*\\]:");
rule.format = qvAppDebugLogFormat;
highlightingRules.append(rule);
}
void SyntaxHighlighter::highlightBlock(const QString &text)
{
for (const HighlightingRule &rule : qAsConst(highlightingRules)) {
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext()) {
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
setCurrentBlockState(0);
}
}
}

View File

@ -1,66 +0,0 @@
#ifndef QVNETSPEEDBARJSON_H
#define QVNETSPEEDBARJSON_H
#include "Qv2rayBase.hpp"
//
#define QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX "Qv2ray_NetSpeed_Widget_LocalSocket"
#define QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_WIN "\\\\.\\pipe\\qv2ray_desktop_netspeed_toolbar_pipe"
//
namespace Qv2ray
{
namespace Components
{
namespace NetSpeedPlugin
{
/// NO NOT CHANGE THE ORDER
static const QMap<int, QString> NetSpeedPluginMessages {
{ 0, QObject::tr("Custom Text")},
// Current Status
{ 101, QObject::tr("Current Time") },
{ 102, QObject::tr("Current Date") },
{ 103, QObject::tr("Current Qv2ray Version") },
{ 104, QObject::tr("Current Connection Name") },
{ 105, QObject::tr("Current Connection Status") },
// Speeds
{ 201, QObject::tr("Total Upload Speed") },
{ 202, QObject::tr("Total Download Speed") },
{ 203, QObject::tr("Upload Speed for Specific Tag") },
{ 204, QObject::tr("Download Speed for Specific Tag") },
// Datas
{ 301, QObject::tr("Total Uploaded Data") },
{ 302, QObject::tr("Total Downloaded Data") },
{ 303, QObject::tr("Uploaded Data for Specific Tag") },
{ 304, QObject::tr("Downloaded Data for Specific Tag") }
};
void StartProcessingPlugins();
void StopProcessingPlugins();
#ifdef Q_OS_WIN
namespace _win
{
void StartNamedPipeThread();
void KillNamedPipeThread();
}
#endif
#ifdef Q_OS_LINUX
namespace _linux
{
// This function is called within a QThread
// Actually it should the entrypoint of a thread.
void StartMessageQThread();
void StopMessageQThread();
}
#endif
QString GetAnswerToRequest(const QString &pchRequest);
}
} // namespace Utils
} // namespace Qv2ray
//
//
//
//
using namespace Qv2ray::Components::NetSpeedPlugin;
#endif // QVNETSPEEDBARJSON_H

View File

@ -1,83 +0,0 @@
#include "QvPACHandler.hpp"
#include "qhttprequest.h"
#include "qhttpresponse.h"
namespace Qv2ray
{
namespace Components
{
PACServer::PACServer() : QObject()
{
pacServer = new QHttpServer();
connect(pacServer, &QHttpServer::newRequest, this, &PACServer::onNewRequest);
}
PACServer::~PACServer()
{
pacServer->close();
delete pacServer;
}
void PACServer::SetProxyString(const QString &proxyString)
{
DEBUG(MODULE_PROXY, "Setting new PAC proxy string: " + proxyString)
this->proxyString = proxyString;
}
void PACServer::StartListen()
{
LOG(MODULE_PROXY, "Starting PAC listener")
pacServer = new QHttpServer();
connect(pacServer, &QHttpServer::newRequest, this, &PACServer::onNewRequest);
//
auto conf = GetGlobalConfig();
auto address = conf.inboundConfig.listenip;
auto port = conf.inboundConfig.pacConfig.port;
//
DEBUG(MODULE_PROXY, "PAC Listening local endpoint: " + address + ":" + QSTRN(port))
//
QString gfwContent = StringFromFile(new QFile(QV2RAY_RULES_GFWLIST_PATH));
pacContent = ConvertGFWToPAC(gfwContent, proxyString);
//
auto result = pacServer->listen(QHostAddress(address), static_cast<ushort>(port));
if (result) {
isStarted = true;
DEBUG(MODULE_PROXY, "Started PAC handler")
} 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::StopServer()
{
if (isStarted) {
pacServer->close();
DEBUG(MODULE_PROXY, "PAC Handler stopped.")
isStarted = false;
delete pacServer;
}
}
void PACServer::onNewRequest(QHttpRequest *req, QHttpResponse *rsp)
{
rsp->setHeader("Server", "Qv2ray/" QV2RAY_VERSION_STRING " PAC_Handler");
if (req->method() == QHttpRequest::HTTP_GET) {
//
if (req->path() == "/pac") {
DEBUG(MODULE_PROXY, "Serving PAC file request.")
//
rsp->setHeader("Content-Type", "application/javascript; charset=utf-8");
rsp->writeHead(QHttpResponse::StatusCode::STATUS_OK);
rsp->end(pacContent.toUtf8());
DEBUG(MODULE_PROXY, "Serving a pac file...")
} else {
rsp->writeHead(QHttpResponse::StatusCode::STATUS_NOT_FOUND);
rsp->end("NOT FOUND");
}
} else {
rsp->writeHead(QHttpResponse::StatusCode::STATUS_METHOD_NOT_ALLOWED);
rsp->end("PAC ONLY SUPPORT GET");
}
}
}
}

View File

@ -1,35 +0,0 @@
#ifndef QVPACHANDLER_H
#define QVPACHANDLER_H
#include "QvUtils.hpp"
#include "qhttpserver.h"
#include <QObject>
namespace Qv2ray
{
namespace Components
{
class PACServer : public QObject
{
Q_OBJECT
public:
explicit PACServer();
~PACServer();
void SetProxyString(const QString &proxyString);
void StartListen();
void StopServer();
QString gfwFilePath;
public slots:
void onNewRequest(QHttpRequest *request, QHttpResponse *response);
private:
bool isStarted;
QHttpServer *pacServer;
QString pacContent;
QString proxyString;
};
}
}
using namespace Qv2ray::Components;
#endif // QVPACHANDLER_H

View File

@ -1,280 +0,0 @@
#include "QvSystemProxyConfigurator.hpp"
#ifdef Q_OS_WIN
#include "wininet.h"
#include <windows.h>
#endif
namespace Qv2ray
{
namespace Components
{
#ifdef Q_OS_MACOS
QStringList macOSgetNetworkServices()
{
QProcess p;
p.start("/usr/sbin/networksetup -listallnetworkservices");
LOG(MODULE_PROXY, p.errorString())
auto str = p.readAllStandardOutput();
auto lines = SplitLines(str);
QStringList result;
// Start from 1 since first line is unneeded.
for (auto i = 1; i < lines.count(); i++) {
// * means disabled.
if (!lines[i].contains("*")) {
result << (lines[i].contains(" ") ? "\"" + lines[i] + "\"" : lines[i]);
}
}
LOG(MODULE_PROXY, "Found " + QSTRN(result.size()) + " network services: " + result.join(";"))
return result;
}
#endif
#ifdef Q_OS_WIN
#define NO_CONST(expr) const_cast<wchar_t *>(expr)
//static auto DEFAULT_CONNECTION_NAME = NO_CONST(L"DefaultConnectionSettings");
///
/// INTERNAL FUNCTION
bool __QueryProxyOptions()
{
INTERNET_PER_CONN_OPTION_LIST List;
INTERNET_PER_CONN_OPTION Option[5];
//
unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
Option[2].dwOption = INTERNET_PER_CONN_FLAGS;
Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
//
List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
List.pszConnection = nullptr;// NO_CONST(DEFAULT_CONNECTION_NAME);
List.dwOptionCount = 5;
List.dwOptionError = 0;
List.pOptions = Option;
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
LOG(MODULE_PROXY, "InternetQueryOption failed, GLE=" + QSTRN(GetLastError()))
}
LOG(MODULE_PROXY, "System default proxy info:")
if (Option[0].Value.pszValue != nullptr) {
LOG(MODULE_PROXY, QString::fromWCharArray(Option[0].Value.pszValue))
}
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) {
LOG(MODULE_PROXY, "PROXY_TYPE_AUTO_PROXY_URL")
}
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) {
LOG(MODULE_PROXY, "PROXY_TYPE_AUTO_DETECT")
}
if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) {
LOG(MODULE_PROXY, "PROXY_TYPE_DIRECT")
}
if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) {
LOG(MODULE_PROXY, "PROXY_TYPE_PROXY")
}
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
LOG(MODULE_PROXY, "InternetQueryOption failed,GLE=" + QSTRN(GetLastError()))
}
if (Option[4].Value.pszValue != nullptr) {
LOG(MODULE_PROXY, QString::fromStdWString(Option[4].Value.pszValue))
}
INTERNET_VERSION_INFO Version;
nSize = sizeof(INTERNET_VERSION_INFO);
InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize);
if (Option[0].Value.pszValue != nullptr) {
GlobalFree(Option[0].Value.pszValue);
}
if (Option[3].Value.pszValue != nullptr) {
GlobalFree(Option[3].Value.pszValue);
}
if (Option[4].Value.pszValue != nullptr) {
GlobalFree(Option[4].Value.pszValue);
}
return false;
}
bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC)
{
INTERNET_PER_CONN_OPTION_LIST list;
BOOL bReturn;
DWORD dwBufSize = sizeof(list);
// Fill the list structure.
list.dwSize = sizeof(list);
// NULL == LAN, otherwise connectoid name.
list.pszConnection = nullptr;
if (isPAC) {
LOG(MODULE_PROXY, "Setting system proxy for PAC")
//
list.dwOptionCount = 2;
list.pOptions = new INTERNET_PER_CONN_OPTION[2];
// Ensure that the memory was allocated.
if (nullptr == list.pOptions) {
// Return FALSE if the memory wasn't allocated.
return FALSE;
}
// Set flags.
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;
// Set proxy name.
list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
list.pOptions[1].Value.pszValue = proxy_full_addr;
} else {
LOG(MODULE_PROXY, "Setting system proxy for Global Proxy")
//
list.dwOptionCount = 3;
list.pOptions = new INTERNET_PER_CONN_OPTION[3];
if (nullptr == list.pOptions) {
return false;
}
// Set flags.
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
// Set proxy name.
list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
list.pOptions[1].Value.pszValue = proxy_full_addr;
// Set proxy override.
list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
auto localhost = L"localhost";
list.pOptions[2].Value.pszValue = NO_CONST(localhost);
}
// Set the options on the connection.
bReturn = InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize);
delete [] list.pOptions;
InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);
InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);
return bReturn;
}
#endif
bool SetSystemProxy(const QString &address, int port, bool usePAC)
{
#ifdef Q_OS_WIN
QString __a;
if (usePAC) {
__a = address;
} else {
__a = "http://" + address + ":" + QSTRN(port);
}
auto proxyStrW = new WCHAR[__a.length() + 1];
wcscpy(proxyStrW, __a.toStdWString().c_str());
//
__QueryProxyOptions();
if (!__SetProxyOptions(proxyStrW, usePAC)) {
LOG(MODULE_PROXY, "Failed to set proxy.")
return false;
}
__QueryProxyOptions();
return true;
#elif defined(Q_OS_LINUX)
bool result = true;
if (usePAC) {
result = result && QProcess::execute("gsettings set org.gnome.system.proxy mode 'auto'") == QProcess::NormalExit;
result = result && QProcess::execute("gsettings set org.gnome.system.proxy autoconfig-url '" + address + "'") == QProcess::NormalExit;
} else {
result = result && QProcess::execute("gsettings set org.gnome.system.proxy mode 'manual'") == QProcess::NormalExit;
result = result && QProcess::execute("gsettings set org.gnome.system.proxy.http host '" + address + "'") == QProcess::NormalExit;
result = result && QProcess::execute("gsettings set org.gnome.system.proxy.http port " + QSTRN(port)) == QProcess::NormalExit;
result = result && QProcess::execute("gsettings set org.gnome.system.proxy.https host '" + address + "'") == QProcess::NormalExit;
result = result && QProcess::execute("gsettings set org.gnome.system.proxy.https port " + QSTRN(port)) == QProcess::NormalExit;
}
if (!result) {
LOG(MODULE_PROXY, "Something wrong happens when setting system proxy -> Gnome ONLY.")
LOG(MODULE_PROXY, "If you are using KDE Plasma and receiving this message, just simply ignore this.")
}
return result;
#else
bool result = true;
for (auto service : macOSgetNetworkServices()) {
LOG(MODULE_PROXY, "Setting proxy for interface: " + service)
if (usePAC) {
result = result && QProcess::execute("/usr/sbin/networksetup -setautoproxystate " + service + " on") == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setautoproxyurl " + service + " " + address) == QProcess::NormalExit;
} else {
result = result && QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " on") == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setsecurewebproxystate " + service + " on") == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setwebproxy " + service + " " + address + " " + QSTRN(port)) == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setsecurewebproxy " + service + " " + address + " " + QSTRN(port)) == QProcess::NormalExit;
}
}
return result;
#endif
}
bool ClearSystemProxy()
{
#ifdef Q_OS_WIN
LOG(MODULE_PROXY, "Cleaning system proxy settings.")
INTERNET_PER_CONN_OPTION_LIST list;
BOOL bReturn;
DWORD dwBufSize = sizeof(list);
// Fill out list struct.
list.dwSize = sizeof(list);
// nullptr == LAN, otherwise connectoid name.
list.pszConnection = nullptr;
// Set three options.
list.dwOptionCount = 1;
list.pOptions = new INTERNET_PER_CONN_OPTION[list.dwOptionCount];
// Make sure the memory was allocated.
if (nullptr == list.pOptions) {
// Return FALSE if the memory wasn't allocated.
LOG(MODULE_PROXY, "Failed to allocat memory in DisableConnectionProxy()")
return FALSE;
}
// Set flags.
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;
//
// Set the options on the connection.
bReturn = InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize);
delete [] list.pOptions;
InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);
InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);
return bReturn;
#elif defined(Q_OS_LINUX)
return QProcess::execute("gsettings set org.gnome.system.proxy mode 'none'") == QProcess::ExitStatus::NormalExit;
#else
bool result = true;
for (auto service : macOSgetNetworkServices()) {
result = result && QProcess::execute("/usr/sbin/networksetup -setautoproxystate " + service + " off") == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " off") == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setsecurewebproxystate " + service + " off") == QProcess::NormalExit;
result = result && QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " off") == QProcess::NormalExit;
}
return result;
#endif
}
}
}

View File

@ -1,17 +0,0 @@
#ifndef QVSYSTEMPROXYCONFIGURATOR_H
#define QVSYSTEMPROXYCONFIGURATOR_H
#include "QvUtils.hpp"
#include <QObject>
//
namespace Qv2ray
{
namespace Components
{
bool ClearSystemProxy();
bool SetSystemProxy(const QString &address, int port, bool usePAC);
}
}
using namespace Qv2ray::Components;
#endif // QVSYSTEMPROXYCONFIGURATOR_H

View File

@ -1,175 +0,0 @@
#include "QvTCPing.hpp"
#include "QtConcurrent/QtConcurrent"
namespace Qv2ray
{
namespace Components
{
QvTCPingModel::QvTCPingModel(int defaultCount, QObject *parent) : QObject(parent)
{
count = defaultCount;
}
void QvTCPingModel::StopAllPing()
{
while (!pingWorkingThreads.isEmpty()) {
auto worker = pingWorkingThreads.dequeue();
worker->future().cancel();
worker->cancel();
}
}
void QvTCPingModel::StartPing(const QvConfigIdentifier &connectionName, const QString &hostName, int port)
{
QvTCPingData data;
data.hostName = hostName;
data.port = port;
data.connectionIdentifier = connectionName;
auto watcher = new QFutureWatcher<QvTCPingData>(this);
DEBUG(MODULE_NETWORK, "Start Ping: " + hostName + ":" + QSTRN(port))
watcher->setFuture(QtConcurrent::run(&QvTCPingModel::startTestLatency, data, count));
pingWorkingThreads.enqueue(watcher);
connect(watcher, &QFutureWatcher<void>::finished, this, [this, watcher]() {
this->pingWorkingThreads.removeOne(watcher);
auto result = watcher->result();
DEBUG(MODULE_NETWORK, "Ping finished: " + result.hostName + ":" + QSTRN(result.port) + " --> " + QSTRN(result.avg) + "ms")
if (!result.errorMessage.isEmpty()) {
LOG(MODULE_NETWORK, "Ping --> " + result.errorMessage)
}
emit this->PingFinished(result);
});
}
QvTCPingData QvTCPingModel::startTestLatency(QvTCPingData data, const int count)
{
if (isExiting()) return QvTCPingData();
double successCount = 0, errorCount = 0;
addrinfo *resolved;
int errcode;
if ((errcode = resolveHost(data.hostName.toStdString(), data.port, &resolved)) != 0) {
#if defined (__WIN32) && defined (UNICODE)
data.errorMessage = QString::fromStdWString(gai_strerror(errcode));
#else
data.errorMessage = gai_strerror(errcode);
#endif
return data;
}
bool noAddress = false;
int currentCount = 0;
while (currentCount < count) {
if (isExiting()) return QvTCPingData();
timeval rtt;
if ((errcode = testLatency(resolved, &rtt)) != 0) {
if (errcode != -EADDRNOTAVAIL) {
LOG(MODULE_NETWORK, "Error connecting to host: " + data.hostName + ":" + QSTRN(data.port) + " " + strerror(-errcode))
errorCount++;
} else {
if (noAddress) {
LOG(MODULE_NETWORK, ".")
} else {
LOG(MODULE_NETWORK, "error connecting to host: " + QSTRN(-errcode) + " " + strerror(-errcode))
}
noAddress = true;
}
} else {
noAddress = false;
successCount++;
double ms = (rtt.tv_sec * 1000.0) + (rtt.tv_usec / 1000.0);
data.avg += ms;
data.min = min(data.min, ms);
data.max = max(data.max, ms);
if (ms > 1000) {
break; /* Stop the test on the first long connect() */
}
}
currentCount++;
QThread::msleep(500);
}
data.avg = data.avg / successCount;
freeaddrinfo(resolved);
return data;
}
int QvTCPingModel::resolveHost(const string &host, int port, addrinfo **res)
{
if (isExiting()) return 0;
addrinfo hints;
#ifdef _WIN32
WSADATA wsadata;
WSAStartup(0x0202, &wsadata);
memset(&hints, 0, sizeof(struct addrinfo));
#else
hints.ai_flags = AI_NUMERICSERV;
#endif
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
return getaddrinfo(host.c_str(), to_string(port).c_str(), &hints, res);
}
int QvTCPingModel::testLatency(struct addrinfo *addr, struct timeval *rtt)
{
if (isExiting()) return 0;
int fd;
struct timeval start;
int connect_result;
const int on = 1;
/* int flags; */
int rv = 0;
/* try to connect for each of the entries: */
while (addr != nullptr) {
if (isExiting()) return 0;
/* create socket */
if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) == -1)
goto next_addr0;
#ifdef _WIN32
// Windows needs special conversion.
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)) < 0)
#else
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
#endif
goto next_addr1;
if (gettimeofday(&start, nullptr) == -1)
goto next_addr1;
/* connect to peer */
// Qt has its own connect() function in QObject....
// So we add "::" here
if ((connect_result = ::connect(fd, addr->ai_addr, addr->ai_addrlen)) == 0) {
if (gettimeofday(rtt, nullptr) == -1)
goto next_addr1;
rtt->tv_sec = rtt->tv_sec - start.tv_sec;
rtt->tv_usec = rtt->tv_usec - start.tv_usec;
close(fd);
return 0;
}
next_addr1:
close(fd);
next_addr0:
addr = addr->ai_next;
}
rv = rv ? rv : -errno;
return rv;
}
}
}

View File

@ -1,52 +0,0 @@
#ifndef QVTCPING_H
#define QVTCPING_H
#ifdef _WIN32
#include <winsock2.h>
#include <ws2def.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif
#include <sys/time.h>
#include <unistd.h>
#include "QvUtils.hpp"
namespace Qv2ray
{
namespace Components
{
struct QvTCPingData {
QvConfigIdentifier connectionIdentifier;
QString hostName;
int port;
QString errorMessage;
int total, succeed, failed;
double min = 999999999999999.0, max = 0.0, avg = 0.0;
};
//
class QvTCPingModel : public QObject
{
Q_OBJECT
public:
explicit QvTCPingModel(int defaultCount = 5, QObject *parent = nullptr);
void StartPing(const QvConfigIdentifier &connectionName, const QString &hostName, int port);
void StopAllPing();
signals:
void PingFinished(QvTCPingData data);
private:
static int resolveHost(const string &host, int portnr, struct addrinfo **res);
static int testLatency(struct addrinfo *addr, struct timeval *rtt);
static QvTCPingData startTestLatency(QvTCPingData data, const int count);
int count;
QQueue<QFutureWatcher<QvTCPingData>*> pingWorkingThreads;
};
}
}
using namespace Qv2ray;
using namespace Qv2ray::Components;
#endif

View File

@ -0,0 +1,186 @@
#include "QvAutoLaunch.hpp"
#include <QSettings>
#include <QApplication>
#include <QDir>
#include <QStandardPaths>
#include <QTextStream>
// macOS headers (possibly OBJ-c)
#if defined(Q_OS_MAC)
#include <CoreServices/CoreServices.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
namespace Qv2ray::components::autolaunch
{
//
// launchatlogin.cpp
// ShadowClash
//
// Created by TheWanderingCoel on 2018/6/12.
// Copyright © 2019 Coel Wu. All rights reserved.
//
QString getUserAutostartDir_private()
{
QString config = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
config += QLatin1String("/autostart/");
return config;
}
bool GetLaunchAtLoginStatus()
{
#ifdef Q_OS_WIN
QString appName = QApplication::applicationName();
QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
return reg.contains(appName);
}
#elif defined Q_OS_MAC
// From https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp
// this is quite some duplicate code with setLaunchOnStartup, at some point we should fix this FIXME.
bool returnValue = false;
QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath();
CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8);
CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true);
LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0);
if (loginItems)
{
// We need to iterate over the items and check which one is "ours".
UInt32 seedValue;
CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);
CFStringRef appUrlRefString = CFURLGetString(urlRef); // no need for release
for (int i = 0; i < CFArrayGetCount(itemsArray); i++) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
CFURLRef itemUrlRef = NULL;
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
returnValue = true;
}
CFRelease(itemUrlRef);
}
}
CFRelease(itemsArray);
}
CFRelease(loginItems);
CFRelease(folderCFStr);
CFRelease(urlRef);
return returnValue;
}
#elif defined Q_OS_LINUX
QString appName = QApplication::applicationName();
QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop");
return QFile::exists(desktopFileLocation);
}
#endif
void SetLaunchAtLoginStatus(bool enable)
{
#ifdef Q_OS_WIN
QString appName = QApplication::applicationName();
QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
if (enable) {
QString strAppPath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
reg.setValue(appName, strAppPath);
} else {
reg.remove(appName);
}
}
#elif defined Q_OS_MAC
// From https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp
QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath();
CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8);
CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true);
LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0);
if (loginItems && enable)
{
//Insert an item to the list.
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems,
kLSSharedFileListItemLast, 0, 0,
urlRef, 0, 0);
if (item)
CFRelease(item);
CFRelease(loginItems);
} else if (loginItems && !enable)
{
// We need to iterate over the items and check which one is "ours".
UInt32 seedValue;
CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);
CFStringRef appUrlRefString = CFURLGetString(urlRef);
for (int i = 0; i < CFArrayGetCount(itemsArray); i++) {
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
CFURLRef itemUrlRef = NULL;
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
LSSharedFileListItemRemove(loginItems, item); // remove it!
}
CFRelease(itemUrlRef);
}
}
CFRelease(itemsArray);
CFRelease(loginItems);
}
CFRelease(folderCFStr);
CFRelease(urlRef);
}
#elif defined Q_OS_LINUX
// From https://github.com/nextcloud/desktop/blob/master/src/common/utility_unix.cpp
QString appName = QApplication::applicationName();
QString userAutoStartPath = getUserAutostartDir_private();
QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
if (enable)
{
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
// qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
return;
}
QFile iniFile(desktopFileLocation);
if (!iniFile.open(QIODevice::WriteOnly)) {
// qCWarning(lcUtility) << "Could not write auto start entry" << desktopFileLocation;
return;
}
QTextStream ts(&iniFile);
ts.setCodec("UTF-8");
ts << QLatin1String("[Desktop Entry]") << endl
<< QLatin1String("Name=") << QApplication::applicationName() << endl
<< QLatin1String("GenericName=") << QLatin1String("V2ray Frontend") << endl
<< QLatin1String("Exec=") << QCoreApplication::applicationFilePath() << endl
<< QLatin1String("Terminal=") << "false" << endl
<< QLatin1String("Icon=") << "qv2ray" << endl // always use lowercase for icons
<< QLatin1String("Categories=") << "Network" << endl
<< QLatin1String("Type=") << "Application" << endl
<< QLatin1String("StartupNotify=") << "false" << endl
<< QLatin1String("X-GNOME-Autostart-enabled=") << "true" << endl;
} else
{
if (!QFile::remove(desktopFileLocation)) {
// qCWarning(lcUtility) << "Could not remove autostart desktop file";
}
}
}
#endif
}

View File

@ -0,0 +1,10 @@
#pragma once
namespace Qv2ray::components::autolaunch
{
bool GetLaunchAtLoginStatus();
void SetLaunchAtLoginStatus(bool enable);
}
using namespace Qv2ray::components;
using namespace Qv2ray::components::autolaunch;

View File

@ -0,0 +1,37 @@
#include "QvGeositeReader.hpp"
#include "libs/gen/v2ray_geosite.pb.h"
namespace Qv2ray::components::geosite
{
QStringList ReadGeoSiteFromFile(QString filepath)
{
QStringList list;
LOG(FILEIO, "Reading geosites from: " + filepath)
//
GOOGLE_PROTOBUF_VERIFY_VERSION;
//
QFile f(filepath);
bool opened = f.open(QFile::OpenModeFlag::ReadOnly);
if (!opened) {
LOG(FILEIO, "File cannot be opened: " + filepath)
return list;
}
auto content = f.readAll();
f.close();
//
GeoSiteList sites;
sites.ParseFromArray(content.data(), content.size());
for (auto e : sites.entry()) {
// We want to use lower string.
list << QString::fromStdString(e.country_code()).toLower();
}
LOG(FILEIO, "Loaded " + QSTRN(list.count()) + " geosite entries from data file.")
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return list;
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include "base/Qv2rayBase.hpp"
namespace Qv2ray::components::geosite
{
QStringList ReadGeoSiteFromFile(QString filepath);
}
using namespace Qv2ray::components;
using namespace Qv2ray::components::geosite;

View File

@ -0,0 +1,133 @@
/* 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);
}
}

View File

@ -0,0 +1,79 @@
#include "QvPACHandler.hpp"
#include "qhttprequest.h"
#include "qhttpresponse.h"
#include "core/CoreUtils.hpp"
#include "common/QvHelpers.hpp"
namespace Qv2ray::components::pac
{
PACServer::PACServer() : QObject(), pacServer(this)
{
connect(&pacServer, &QHttpServer::newRequest, this, &PACServer::onNewRequest);
}
PACServer::~PACServer()
{
if (isStarted) {
pacServer.close();
}
}
void PACServer::SetProxyString(const QString &proxyString)
{
DEBUG(PROXY, "Setting new PAC proxy string: " + proxyString)
this->proxyString = proxyString;
}
void PACServer::StartListen()
{
LOG(PROXY, "Starting PAC listener")
//
auto address = GlobalConfig.inboundConfig.listenip;
auto port = GlobalConfig.inboundConfig.pacConfig.port;
//
DEBUG(PROXY, "PAC Listening local endpoint: " + address + ":" + QSTRN(port))
//
QString gfwContent = StringFromFile(QV2RAY_RULES_GFWLIST_PATH);
pacContent = ConvertGFWToPAC(gfwContent, proxyString);
//
auto result = pacServer.listen(QHostAddress(address), static_cast<ushort>(port));
if (result) {
isStarted = true;
DEBUG(PROXY, "Started PAC handler")
} else {
LOG(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::StopServer()
{
if (isStarted) {
pacServer.close();
DEBUG(PROXY, "PAC Handler stopped.")
isStarted = false;
}
}
void PACServer::onNewRequest(QHttpRequest *req, QHttpResponse *rsp)
{
rsp->setHeader("Server", "Qv2ray/" QV2RAY_VERSION_STRING " PAC_Handler");
if (req->method() == QHttpRequest::HTTP_GET) {
//
if (req->path() == "/pac") {
DEBUG(PROXY, "Serving PAC file request.")
//
rsp->setHeader("Content-Type", "application/javascript; charset=utf-8");
rsp->writeHead(QHttpResponse::StatusCode::STATUS_OK);
rsp->end(pacContent.toUtf8());
DEBUG(PROXY, "Serving a pac file...")
} else {
rsp->writeHead(QHttpResponse::StatusCode::STATUS_NOT_FOUND);
rsp->end("NOT FOUND");
}
} else {
rsp->writeHead(QHttpResponse::StatusCode::STATUS_METHOD_NOT_ALLOWED);
rsp->end("PAC ONLY SUPPORT GET");
}
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "qhttpserver.h"
#include <QObject>
#include <memory>
namespace Qv2ray::components::pac
{
QString ConvertGFWToPAC(const QString &rawContent, const QString &customProxyString);
class PACServer : public QObject
{
Q_OBJECT
public:
explicit PACServer();
~PACServer();
void SetProxyString(const QString &proxyString);
void StartListen();
void StopServer();
QString gfwFilePath;
public slots:
void onNewRequest(QHttpRequest *request, QHttpResponse *response);
private:
bool isStarted;
QHttpServer pacServer;
QString pacContent;
QString proxyString;
};
}
using namespace Qv2ray::components;
using namespace Qv2ray::components::pac;

View File

@ -0,0 +1,158 @@
#include <QThread>
#include "ui/w_MainWindow.hpp"
#include "components/plugins/toolbar/QvToolbar.hpp"
#include "common/QvHelpers.hpp"
namespace Qv2ray::components::plugins
{
namespace Toolbar
{
void StopProcessingPlugins()
{
#ifdef Q_OS_LINUX
_linux::StopMessageQThread();
#endif
#ifdef Q_OS_WIN
_win::KillNamedPipeThread();
#endif
}
/// Public Function - CALL ONLY ONCE -
/// To start processing plugins' command.
void StartProcessingPlugins()
{
#ifdef Q_OS_LINUX
_linux::StartMessageQThread();
#endif
#ifdef Q_OS_WIN
_win::StartNamedPipeThread();
#endif
}
QString GetAnswerToRequest(const QString &pchRequest)
{
auto instance = MainWindow::mwInstance;
if (instance == nullptr || instance->vinstance == nullptr) {
LOG(PLUGIN, "MainWindow != nullptr Assertion failed!")
return "{}";
}
auto vinstance = instance->vinstance;
//
auto req = pchRequest.trimmed();
QString reply = "{}";
if (req == "START") {
emit instance->Connect();
} else if (req == "STOP") {
emit instance->DisConnect();
} else if (req == "RESTART") {
emit instance->ReConnect();
}
auto BarConfig = GlobalConfig.toolBarConfig;
for (auto i = 0; i < BarConfig.Pages.size(); i++) {
for (auto j = 0; j < BarConfig.Pages[i].Lines.size(); j++) {
#define CL BarConfig.Pages[i].Lines[j]
switch (CL.ContentType) {
case 0: {
// Custom Text
// We do nothing...
break;
}
case 101: {
// Current Time
CL.Message = QTime().currentTime().toString("hh:mm:ss");
break;
}
case 102: {
// Current Date
CL.Message = QDate().currentDate().toString("yyyy-MM-dd");
break;
}
case 103: {
// Current Qv2ray Version
CL.Message = QV2RAY_VERSION_STRING;
break;
}
case 104: {
// Current Connection Name
CL.Message = instance->GetCurrentConnectedConfigName();
break;
}
case 105: {
// Current Connection Status
CL.Message = instance->vinstance->KernelStarted
? QObject::tr("Connected")
: QObject::tr("Disconnected");
break;
}
case 201: {
// Total upload speed;
CL.Message = FormatBytes(vinstance->getAllSpeedUp()) + "/s";
break;
}
case 202: {
// Total download speed;
CL.Message = FormatBytes(vinstance->getAllSpeedDown()) + "/s";
break;
}
case 203: {
// Upload speed for tag
CL.Message = FormatBytes(vinstance->getTagSpeedUp(CL.Message)) + "/s";
break;
}
case 204: {
// Download speed for tag
CL.Message = FormatBytes(vinstance->getTagSpeedDown(CL.Message)) + "/s";
break;
}
case 301: {
// Total Upload
CL.Message = FormatBytes(vinstance->getAllDataUp());
break;
}
case 302: {
// Total download
CL.Message = FormatBytes(vinstance->getAllDataDown());
break;
}
case 303: {
// Upload for tag
CL.Message = FormatBytes(vinstance->getTagDataUp(CL.Message));
break;
}
case 304: {
// Download for tag
CL.Message = FormatBytes(vinstance->getTagDataDown(CL.Message));
break;
}
default: {
CL.Message = "Not Supported?";
break;
}
}
}
}
reply = StructToJsonString(BarConfig);
return reply;
}
}
}

View File

@ -0,0 +1,57 @@
#pragma once
#include "base/Qv2rayBase.hpp"
#define QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX "Qv2ray_NetSpeed_Widget_LocalSocket"
#define QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_WIN "\\\\.\\pipe\\qv2ray_desktop_netspeed_toolbar_pipe"
namespace Qv2ray::components::plugins
{
namespace Toolbar
{
/// NO NOT CHANGE THE ORDER
static const QMap<int, QString> NetSpeedPluginMessages {
{ 0, QObject::tr("Custom Text")},
// Current Status
{ 101, QObject::tr("Current Time") },
{ 102, QObject::tr("Current Date") },
{ 103, QObject::tr("Current Qv2ray Version") },
{ 104, QObject::tr("Current Connection Name") },
{ 105, QObject::tr("Current Connection Status") },
// Speeds
{ 201, QObject::tr("Total Upload Speed") },
{ 202, QObject::tr("Total Download Speed") },
{ 203, QObject::tr("Upload Speed for Specific Tag") },
{ 204, QObject::tr("Download Speed for Specific Tag") },
// Datas
{ 301, QObject::tr("Total Uploaded Data") },
{ 302, QObject::tr("Total Downloaded Data") },
{ 303, QObject::tr("Uploaded Data for Specific Tag") },
{ 304, QObject::tr("Downloaded Data for Specific Tag") }
};
void StartProcessingPlugins();
void StopProcessingPlugins();
#ifdef Q_OS_WIN
namespace _win
{
void StartNamedPipeThread();
void KillNamedPipeThread();
}
#endif
#ifdef Q_OS_LINUX
namespace _linux
{
// This function is called within a QThread
// Actually it should the entrypoint of a thread.
void StartMessageQThread();
void StopMessageQThread();
}
#endif
QString GetAnswerToRequest(const QString &pchRequest);
}
}
using namespace Qv2ray::components;
using namespace Qv2ray::components::plugins::Toolbar;

View File

@ -0,0 +1,84 @@
#include <QtCore>
#ifdef Q_OS_LINUX
#include "components/plugins/toolbar/QvToolbar.hpp"
#include "common/QvHelpers.hpp"
#include <QLocalSocket>
#include <QLocalServer>
namespace Qv2ray::components::plugins::Toolbar
{
namespace _linux
{
static QThread *linuxWorkerThread;
static QLocalServer *server;
static volatile bool isExiting = false;
void qobject_proxy()
{
QLocalSocket *socket = server->nextPendingConnection();
if (!socket->waitForConnected() || !socket->waitForReadyRead())
return;
try {
while (!isExiting && socket->isOpen() && socket->isValid() && socket->waitForReadyRead()) {
// CANNOT PROPERLY READ...
// Temp-ly fixed (but why and how?)
auto in = QString(socket->readAll());
if (!isExiting && !in.isEmpty()) {
auto out = GetAnswerToRequest(in);
//
socket->write(out.toUtf8());
socket->flush();
} else {
QThread::msleep(200);
}
}
} catch (...) {
LOG(PLUGIN, "Closing a broken socket.")
}
}
void DataMessageQThread()
{
server = new QLocalServer();
// BUG Sometimes failed to listen due to improper close of last session.
bool listening = server->listen(QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX);
while (!isExiting && !listening) {
QThread::msleep(500);
listening = server->listen(QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX);
}
bool timeOut = false;
server->setSocketOptions(QLocalServer::WorldAccessOption);
QObject::connect(server, &QLocalServer::newConnection, &qobject_proxy);
while (!isExiting) {
bool result = server->waitForNewConnection(5000, &timeOut);
DEBUG(PLUGIN, "Plugin thread listening failed: " + server->errorString())
DEBUG(PLUGIN, "waitForNewConnection: " + QString(result ? "true" : "false") + ", " + QString(timeOut ? "true" : "false"))
}
server->close();
delete server;
}
void StartMessageQThread()
{
linuxWorkerThread = QThread::create(_linux::DataMessageQThread);
linuxWorkerThread->start();
}
void StopMessageQThread()
{
isExiting = true;
if (linuxWorkerThread->isRunning()) {
LOG(PLUGIN, "Waiting for linuxWorkerThread to stop.")
linuxWorkerThread->wait();
}
delete _linux::linuxWorkerThread;
}
}
}
#endif

View File

@ -0,0 +1,114 @@
#include <QtCore>
#ifdef Q_OS_WIN
#include "components/plugins/toolbar/QvToolbar.hpp"
#include "common/QvHelpers.hpp"
#include <windows.h>
namespace Qv2ray::components::plugins::Toolbar
{
namespace _win
{
// Private Headers
constexpr int BUFSIZE = 10240;
DWORD WINAPI NamedPipeMasterThread(LPVOID lpvParam);
DWORD WINAPI InstanceThread(LPVOID);
static LPVOID ThreadHandle;
static bool isExiting = false;
//
void KillNamedPipeThread()
{
isExiting = true;
TerminateThread(ThreadHandle, 0);
}
//
void StartNamedPipeThread()
{
auto hThread = CreateThread(nullptr, 0, NamedPipeMasterThread, nullptr, 0, nullptr);
if (hThread == nullptr) {
LOG(PLUGIN, "CreateThread failed, GLE=" + QSTRN(GetLastError()))
return;
} else CloseHandle(hThread);
}
DWORD WINAPI NamedPipeMasterThread(LPVOID lpvParam)
{
Q_UNUSED(lpvParam)
BOOL fConnected = FALSE;
DWORD dwThreadId = 0;
HANDLE hPipe = INVALID_HANDLE_VALUE;
auto lpszPipename = QString(QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_WIN).toStdWString();
while (!isExiting) {
//printf("Pipe Server: Main thread awaiting client connection on %s\n", lpszPipename.c_str());
hPipe = CreateNamedPipe(lpszPipename.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, nullptr);
if (hPipe == INVALID_HANDLE_VALUE) {
LOG(PLUGIN, "CreateNamedPipe failed, GLE=" + QSTRN(GetLastError()))
return static_cast<DWORD>(-1);
}
fConnected = ConnectNamedPipe(hPipe, nullptr) ? true : (GetLastError() == ERROR_PIPE_CONNECTED);
if (fConnected) {
LOG(PLUGIN, "Client connected, creating a processing thread")
ThreadHandle = CreateThread(nullptr, 0, InstanceThread, hPipe, 0, &dwThreadId);
if (ThreadHandle == nullptr) {
LOG(PLUGIN, "CreateThread failed, GLE=" + QSTRN(GetLastError()))
return static_cast<DWORD>(-1);
} else CloseHandle(ThreadHandle);
} else CloseHandle(hPipe);
}
return 0;
}
DWORD WINAPI InstanceThread(LPVOID lpvParam)
{
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = false;
HANDLE hPipe = static_cast<HANDLE>(lpvParam);
TCHAR pchRequest[BUFSIZE] = { 0 };
while (!isExiting) {
fSuccess = ReadFile(hPipe, pchRequest, BUFSIZE * sizeof(TCHAR), &cbBytesRead, nullptr);
if (!fSuccess || cbBytesRead == 0) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
LOG(PLUGIN, "InstanceThread: client disconnected, GLE=" + QSTRN(GetLastError()))
} else {
LOG(PLUGIN, "InstanceThread ReadFile failed, GLE=" + QSTRN(GetLastError()))
}
break;
}
auto req = QString::fromStdWString(pchRequest);
QString replyQString = "{}";
if (!isExiting) {
replyQString = GetAnswerToRequest(req);
//
// REPLY as std::string
std::string pchReply = replyQString.toUtf8().constData();
cbReplyBytes = static_cast<DWORD>(pchReply.length() + 1) * sizeof(CHAR);
//cbReplyBytes = static_cast<DWORD>(replyQString.length() + 1) * sizeof(TCHAR);
//
fSuccess = WriteFile(hPipe, pchReply.c_str(), cbReplyBytes, &cbWritten, nullptr);
if (!fSuccess || cbReplyBytes != cbWritten) {
LOG(PLUGIN, "InstanceThread WriteFile failed, GLE=" + QSTRN(GetLastError()))
break;
}
}
}
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
return 1;
}
}
}
#endif

View File

@ -0,0 +1,318 @@
#include "QvProxyConfigurator.hpp"
#include "common/QvHelpers.hpp"
#ifdef Q_OS_WIN
#include "wininet.h"
#include <windows.h>
#endif
namespace Qv2ray::components::proxy
{
#ifdef Q_OS_MACOS
QStringList macOSgetNetworkServices()
{
QProcess p;
p.setProgram("/usr/sbin/networksetup");
p.setArguments(QStringList() << "-listallnetworkservices");
p.start();
p.waitForStarted();
p.waitForFinished();
LOG(PROXY, p.errorString())
auto str = p.readAllStandardOutput();
auto lines = SplitLines(str);
QStringList result;
// Start from 1 since first line is unneeded.
for (auto i = 1; i < lines.count(); i++) {
// * means disabled.
if (!lines[i].contains("*")) {
result << (lines[i].contains(" ") ? "\"" + lines[i] + "\"" : lines[i]);
}
}
LOG(PROXY, "Found " + QSTRN(result.size()) + " network services: " + result.join(";"))
return result;
}
#endif
#ifdef Q_OS_WIN
#define NO_CONST(expr) const_cast<wchar_t *>(expr)
//static auto DEFAULT_CONNECTION_NAME = NO_CONST(L"DefaultConnectionSettings");
///
/// INTERNAL FUNCTION
bool __QueryProxyOptions()
{
INTERNET_PER_CONN_OPTION_LIST List;
INTERNET_PER_CONN_OPTION Option[5];
//
unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
Option[2].dwOption = INTERNET_PER_CONN_FLAGS;
Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
//
List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
List.pszConnection = nullptr;// NO_CONST(DEFAULT_CONNECTION_NAME);
List.dwOptionCount = 5;
List.dwOptionError = 0;
List.pOptions = Option;
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
LOG(PROXY, "InternetQueryOption failed, GLE=" + QSTRN(GetLastError()))
}
LOG(PROXY, "System default proxy info:")
if (Option[0].Value.pszValue != nullptr) {
LOG(PROXY, QString::fromWCharArray(Option[0].Value.pszValue))
}
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) {
LOG(PROXY, "PROXY_TYPE_AUTO_PROXY_URL")
}
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) {
LOG(PROXY, "PROXY_TYPE_AUTO_DETECT")
}
if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) {
LOG(PROXY, "PROXY_TYPE_DIRECT")
}
if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) {
LOG(PROXY, "PROXY_TYPE_PROXY")
}
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
LOG(PROXY, "InternetQueryOption failed,GLE=" + QSTRN(GetLastError()))
}
if (Option[4].Value.pszValue != nullptr) {
LOG(PROXY, QString::fromStdWString(Option[4].Value.pszValue))
}
INTERNET_VERSION_INFO Version;
nSize = sizeof(INTERNET_VERSION_INFO);
InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize);
if (Option[0].Value.pszValue != nullptr) {
GlobalFree(Option[0].Value.pszValue);
}
if (Option[3].Value.pszValue != nullptr) {
GlobalFree(Option[3].Value.pszValue);
}
if (Option[4].Value.pszValue != nullptr) {
GlobalFree(Option[4].Value.pszValue);
}
return false;
}
bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC)
{
INTERNET_PER_CONN_OPTION_LIST list;
BOOL bReturn;
DWORD dwBufSize = sizeof(list);
// Fill the list structure.
list.dwSize = sizeof(list);
// NULL == LAN, otherwise connectoid name.
list.pszConnection = nullptr;
if (isPAC) {
LOG(PROXY, "Setting system proxy for PAC")
//
list.dwOptionCount = 2;
list.pOptions = new INTERNET_PER_CONN_OPTION[2];
// Ensure that the memory was allocated.
if (nullptr == list.pOptions) {
// Return FALSE if the memory wasn't allocated.
return FALSE;
}
// Set flags.
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;
// Set proxy name.
list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
list.pOptions[1].Value.pszValue = proxy_full_addr;
} else {
LOG(PROXY, "Setting system proxy for Global Proxy")
//
list.dwOptionCount = 3;
list.pOptions = new INTERNET_PER_CONN_OPTION[3];
if (nullptr == list.pOptions) {
return false;
}
// Set flags.
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
// Set proxy name.
list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
list.pOptions[1].Value.pszValue = proxy_full_addr;
// Set proxy override.
list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
auto localhost = L"localhost";
list.pOptions[2].Value.pszValue = NO_CONST(localhost);
}
// Set the options on the connection.
bReturn = InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize);
delete [] list.pOptions;
InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);
InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);
return bReturn;
}
#endif
void SetSystemProxy(const QString &address, int httpPort, int socksPort, bool usePAC)
{
bool hasHTTP = (httpPort != 0);
bool hasSOCKS = (socksPort != 0);
if (!(hasHTTP || hasSOCKS || usePAC)) {
LOG(PROXY, "Nothing?")
return;
}
if (usePAC) {
LOG(PROXY, "Qv2ray will set system proxy to use PAC file")
} else {
if (hasHTTP) {
LOG(PROXY, "Qv2ray will set system proxy to use HTTP")
}
if (hasSOCKS) {
LOG(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);
}
LOG(PROXY, "Windows proxy string: " + __a)
auto proxyStrW = new WCHAR[__a.length() + 1];
wcscpy(proxyStrW, __a.toStdWString().c_str());
//
__QueryProxyOptions();
if (!__SetProxyOptions(proxyStrW, usePAC)) {
LOG(PROXY, "Failed to set proxy.")
}
__QueryProxyOptions();
#elif defined(Q_OS_LINUX)
QStringList actions;
auto proxyMode = usePAC ? "auto" : "manual";
actions << QString("gsettings set org.gnome.system.proxy mode '%1'").arg(proxyMode);
if (usePAC) {
actions << QString("gsettings set org.gnome.system.proxy autoconfig-url '%1'").arg(address);
} else {
if (hasHTTP) {
actions << QString("gsettings set org.gnome.system.proxy.http host '%1'").arg(address);
actions << QString("gsettings set org.gnome.system.proxy.http port %1").arg(httpPort);
//
actions << QString("gsettings set org.gnome.system.proxy.https host '%1'").arg(address);
actions << QString("gsettings set org.gnome.system.proxy.https port %1").arg(httpPort);;
}
if (hasSOCKS) {
actions << QString("gsettings set org.gnome.system.proxy.socks host '%1'").arg(address);
actions << QString("gsettings set org.gnome.system.proxy.socks port %1").arg(socksPort);
}
}
// note: do not use std::all_of / any_of / none_of,
// because those are short-circuit and cannot guarantee atomicity.
auto result = std::count_if(actions.cbegin(), actions.cend(), [](const QString & action) {
DEBUG(PROXY, action)
return QProcess::execute(action) == QProcess::NormalExit;
}) == actions.size();
if (!result) {
LOG(PROXY, "Something wrong happens when setting system proxy -> Gnome ONLY.")
LOG(PROXY, "If you are using KDE Plasma and receiving this message, just simply ignore this.")
}
Q_UNUSED(result);
#else
for (auto service : macOSgetNetworkServices()) {
LOG(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");
QProcess::execute("/usr/sbin/networksetup -setsecurewebproxystate " + service + " on");
QProcess::execute("/usr/sbin/networksetup -setwebproxy " + service + " " + address + " " + QSTRN(httpPort));
QProcess::execute("/usr/sbin/networksetup -setsecurewebproxy " + service + " " + address + " " + QSTRN(httpPort));
}
if (hasSOCKS) {
QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " on");
QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxy " + service + " " + address + " " + QSTRN(socksPort));
}
}
}
#endif
}
void ClearSystemProxy()
{
#ifdef Q_OS_WIN
LOG(PROXY, "Cleaning system proxy settings.")
INTERNET_PER_CONN_OPTION_LIST list;
BOOL bReturn;
DWORD dwBufSize = sizeof(list);
// Fill out list struct.
list.dwSize = sizeof(list);
// nullptr == LAN, otherwise connectoid name.
list.pszConnection = nullptr;
// Set three options.
list.dwOptionCount = 1;
list.pOptions = new INTERNET_PER_CONN_OPTION[list.dwOptionCount];
// Make sure the memory was allocated.
if (nullptr == list.pOptions) {
// Return FALSE if the memory wasn't allocated.
LOG(PROXY, "Failed to allocat memory in DisableConnectionProxy()")
}
// Set flags.
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;
//
// Set the options on the connection.
bReturn = InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize);
delete [] list.pOptions;
InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);
InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);
#elif defined(Q_OS_LINUX)
QProcess::execute("gsettings set org.gnome.system.proxy mode 'none'");
#else
for (auto service : macOSgetNetworkServices()) {
LOG(PROXY, "Clearing proxy for interface: " + service)
QProcess::execute("/usr/sbin/networksetup -setautoproxystate " + service + " off");
QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " off");
QProcess::execute("/usr/sbin/networksetup -setsecurewebproxystate " + service + " off");
QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " off");
}
#endif
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <QString>
#include <QObject>
//
namespace Qv2ray::components::proxy
{
void ClearSystemProxy();
void SetSystemProxy(const QString &address, int http_port, int socks_port, bool usePAC);
}
using namespace Qv2ray::components;
using namespace Qv2ray::components::proxy;

View File

@ -0,0 +1,352 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "speedplotview.hpp"
#include <QLocale>
#include <QPainter>
#include <QPen>
#include <list>
#include <QCoreApplication>
#define VIEWABLE 60
// use binary prefix standards from IEC 60027-2
// see http://en.wikipedia.org/wiki/Kilobyte
enum class SizeUnit {
Byte, // 1024^0,
KibiByte, // 1024^1,
MebiByte, // 1024^2,
GibiByte, // 1024^3,
TebiByte, // 1024^4,
PebiByte, // 1024^5,
ExbiByte // 1024^6,
// int64 is used for sizes and thus the next units can not be handled
// ZebiByte, // 1024^7,
// YobiByte, // 1024^8
};
namespace
{
const struct {
const char *source;
const char *comment;
} units[] = {
QT_TRANSLATE_NOOP3("misc", "B", "bytes"),
QT_TRANSLATE_NOOP3("misc", "KiB", "kibibytes (1024 bytes)"),
QT_TRANSLATE_NOOP3("misc", "MiB", "mebibytes (1024 kibibytes)"),
QT_TRANSLATE_NOOP3("misc", "GiB", "gibibytes (1024 mibibytes)"),
QT_TRANSLATE_NOOP3("misc", "TiB", "tebibytes (1024 gibibytes)"),
QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
};
}
QString unitString(const SizeUnit unit, const bool isSpeed)
{
const auto &unitString = units[static_cast<int>(unit)];
QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
if (isSpeed)
ret += QCoreApplication::translate("misc", "/s", "per second");
return ret;
}
int friendlyUnitPrecision(const SizeUnit unit)
{
// friendlyUnit's number of digits after the decimal point
switch (unit) {
case SizeUnit::Byte:
return 0;
case SizeUnit::KibiByte:
case SizeUnit::MebiByte:
return 1;
case SizeUnit::GibiByte:
return 2;
default:
return 3;
}
}
qlonglong sizeInBytes(qreal size, const SizeUnit unit)
{
for (int i = 0; i < static_cast<int>(unit); ++i)
size *= 1024;
return size;
}
template <typename T>
constexpr typename std::add_const<T>::type &asConst(T &t) noexcept
{
return t;
}
// Forward rvalue as const
template <typename T>
constexpr typename std::add_const<T>::type asConst(T &&t) noexcept
{
return std::move(t);
}
// Prevent const rvalue arguments
template <typename T>
void asConst(const T &&) = delete;
namespace
{
// table of supposed nice steps for grid marks to get nice looking quarters of scale
const double roundingTable[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
struct SplittedValue {
double arg;
SizeUnit unit;
qint64 sizeInBytes() const
{
return ::sizeInBytes(arg, unit);
}
};
SplittedValue getRoundedYScale(double value)
{
if (value == 0.0) return {0, SizeUnit::Byte};
if (value <= 12.0) return {12, SizeUnit::Byte};
SizeUnit calculatedUnit = SizeUnit::Byte;
while (value > 1024) {
value /= 1024;
calculatedUnit = static_cast<SizeUnit>(static_cast<int>(calculatedUnit) + 1);
}
if (value > 100.0) {
int roundedValue = static_cast<int>(value / 40) * 40;
while (roundedValue < value)
roundedValue += 40;
return {static_cast<double>(roundedValue), calculatedUnit};
}
if (value > 10.0) {
int roundedValue = static_cast<int>(value / 4) * 4;
while (roundedValue < value)
roundedValue += 4;
return {static_cast<double>(roundedValue), calculatedUnit};
}
for (const auto &roundedValue : roundingTable) {
if (value <= roundedValue)
return {roundedValue, calculatedUnit};
}
return {10.0, calculatedUnit};
}
QString formatLabel(const double argValue, const SizeUnit unit)
{
// check is there need for digits after decimal separator
const int precision = (argValue < 10) ? friendlyUnitPrecision(unit) : 0;
return QLocale::system().toString(argValue, 'f', precision)
+ QString::fromUtf8(" ")
+ unitString(unit, true);
}
}
SpeedPlotView::SpeedPlotView(QWidget *parent)
: QGraphicsView(parent)
, m_currentData(&m_datahalfMin)
{
QPen greenPen;
greenPen.setWidthF(1.5);
greenPen.setColor(QColor(134, 196, 63));
QPen bluePen;
bluePen.setWidthF(1.5);
bluePen.setColor(QColor(50, 153, 255));
m_properties[UP] = GraphProperties(tr("Total Upload"), bluePen);
m_properties[DOWN] = GraphProperties(tr("Total Download"), greenPen);
}
void SpeedPlotView::Clear()
{
m_datahalfMin.clear();
replot();
}
void SpeedPlotView::pushPoint(const SpeedPlotView::PointData &point)
{
m_datahalfMin.push_back(point);
while (m_datahalfMin.length() > VIEWABLE) {
m_datahalfMin.removeFirst();
}
}
void SpeedPlotView::replot()
{
viewport()->update();
}
QList<SpeedPlotView::PointData> &SpeedPlotView::getCurrentData()
{
return *m_currentData;
}
quint64 SpeedPlotView::maxYValue()
{
auto &queue = getCurrentData();
quint64 maxYValue = 0;
for (int id = UP; id < NB_GRAPHS; ++id) {
// 30 is half min
for (int i = queue.size() - 1, j = 0; (i >= 0) && (j <= VIEWABLE); --i, ++j) {
if (queue[i].y[id] > maxYValue)
maxYValue = queue[i].y[id];
}
}
return maxYValue;
}
void SpeedPlotView::paintEvent(QPaintEvent *)
{
QPainter painter(viewport());
QRect fullRect = viewport()->rect();
QRect rect = viewport()->rect();
QFontMetrics fontMetrics = painter.fontMetrics();
rect.adjust(4, 4, 0, -4); // Add padding
const SplittedValue niceScale = getRoundedYScale(maxYValue());
rect.adjust(0, fontMetrics.height(), 0, 0); // Add top padding for top speed text
// draw Y axis speed labels
const QVector<QString> speedLabels = {
formatLabel(niceScale.arg, niceScale.unit),
formatLabel((0.75 * niceScale.arg), niceScale.unit),
formatLabel((0.50 * niceScale.arg), niceScale.unit),
formatLabel((0.25 * niceScale.arg), niceScale.unit),
formatLabel(0.0, niceScale.unit),
};
int yAxisWidth = 0;
for (const QString &label : speedLabels)
if (fontMetrics.horizontalAdvance(label) > yAxisWidth)
yAxisWidth = fontMetrics.horizontalAdvance(label);
int i = 0;
for (const QString &label : speedLabels) {
QRectF labelRect(rect.topLeft() + QPointF(-yAxisWidth, (i++) * 0.25 * rect.height() - fontMetrics.height()),
QSizeF(2 * yAxisWidth, fontMetrics.height()));
painter.drawText(labelRect, label, Qt::AlignRight | Qt::AlignTop);
}
// draw grid lines
rect.adjust(yAxisWidth + 4, 0, 0, 0);
QPen gridPen;
gridPen.setStyle(Qt::DashLine);
gridPen.setWidthF(1);
gridPen.setColor(QColor(128, 128, 128, 128));
painter.setPen(gridPen);
painter.drawLine(fullRect.left(), rect.top(), rect.right(), rect.top());
painter.drawLine(fullRect.left(), rect.top() + 0.25 * rect.height(), rect.right(), rect.top() + 0.25 * rect.height());
painter.drawLine(fullRect.left(), rect.top() + 0.50 * rect.height(), rect.right(), rect.top() + 0.50 * rect.height());
painter.drawLine(fullRect.left(), rect.top() + 0.75 * rect.height(), rect.right(), rect.top() + 0.75 * rect.height());
painter.drawLine(fullRect.left(), rect.bottom(), rect.right(), rect.bottom());
const int TIME_AXIS_DIVISIONS = 6;
for (int i = 0; i < TIME_AXIS_DIVISIONS; ++i) {
const int x = rect.left() + (i * rect.width()) / TIME_AXIS_DIVISIONS;
painter.drawLine(x, fullRect.top(), x, fullRect.bottom());
}
// Set antialiasing for graphs
painter.setRenderHints(QPainter::Antialiasing);
// draw graphs
rect.adjust(3, 0, 0, 0); // Need, else graphs cross left gridline
const double yMultiplier = (niceScale.arg == 0.0) ? 0.0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes());
const double xTickSize = static_cast<double>(rect.width()) / VIEWABLE;
auto &queue = getCurrentData();
for (int id = UP; id < NB_GRAPHS; ++id) {
QVector<QPoint> points;
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j <= VIEWABLE); --i, ++j) {
int newX = rect.right() - j * xTickSize;
int newY = rect.bottom() - queue[i].y[id] * yMultiplier;
points.push_back(QPoint(newX, newY));
}
painter.setPen(m_properties[static_cast<GraphID>(id)].pen);
painter.drawPolyline(points.data(), points.size());
}
// draw legend
QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4);
double legendHeight = 0;
int legendWidth = 0;
for (const auto &property : asConst(m_properties)) {
if (fontMetrics.horizontalAdvance(property.name) > legendWidth)
legendWidth = fontMetrics.horizontalAdvance(property.name);
legendHeight += 1.5 * fontMetrics.height();
}
QRectF legendBackgroundRect(QPoint(legendTopLeft.x() - 4, legendTopLeft.y() - 4), QSizeF(legendWidth + 8, legendHeight + 8));
QColor legendBackgroundColor = QWidget::palette().color(QWidget::backgroundRole());
legendBackgroundColor.setAlpha(128); // 50% transparent
painter.fillRect(legendBackgroundRect, legendBackgroundColor);
i = 0;
for (const auto &property : asConst(m_properties)) {
int nameSize = fontMetrics.horizontalAdvance(property.name);
double indent = 1.5 * (i++) * fontMetrics.height();
painter.setPen(property.pen);
painter.drawLine(legendTopLeft + QPointF(0, indent + fontMetrics.height()),
legendTopLeft + QPointF(nameSize, indent + fontMetrics.height()));
painter.drawText(QRectF(legendTopLeft + QPointF(0, indent), QSizeF(2 * nameSize, fontMetrics.height())),
property.name, QTextOption(Qt::AlignVCenter));
}
}
SpeedPlotView::GraphProperties::GraphProperties()
{
}
SpeedPlotView::GraphProperties::GraphProperties(const QString &name, const QPen &pen)
: name(name)
, pen(pen)
{
}

Some files were not shown because too many files have changed in this diff Show More