mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 22:43:24 +00:00
Compare commits
220 Commits
4.8.4.4
...
feat_add_w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cdd41c833 | ||
|
|
d859b111ca | ||
|
|
52031efc48 | ||
|
|
d78202c612 | ||
|
|
6bac948633 | ||
|
|
a4c4ef71fb | ||
|
|
127f85f4f0 | ||
|
|
13d4ddd292 | ||
|
|
7265e09c85 | ||
|
|
2e629b6dac | ||
|
|
92aba49705 | ||
|
|
bec06b3a5e | ||
|
|
91cd9474ea | ||
|
|
6178b05643 | ||
|
|
46ce22b85c | ||
|
|
36edafb985 | ||
|
|
d77eaba500 | ||
|
|
6a3d43fbb0 | ||
|
|
4975955bbe | ||
|
|
8f508783e3 | ||
|
|
f50817c43c | ||
|
|
54f67b3d82 | ||
|
|
d669adb707 | ||
|
|
5103bc640e | ||
|
|
3e6f0c0342 | ||
|
|
40950b92ee | ||
|
|
ac77b4ee75 | ||
|
|
fbf652f818 | ||
|
|
bbbf4891e6 | ||
|
|
20d005d66c | ||
|
|
c81ae2b060 | ||
|
|
105c42db1c | ||
|
|
89818ff63d | ||
|
|
414c422177 | ||
|
|
b39ac8556c | ||
|
|
5e1742262d | ||
|
|
5a07a1274f | ||
|
|
7b8ff1fd6e | ||
|
|
c7221832e0 | ||
|
|
eb7d031c7d | ||
|
|
3b3a0aaceb | ||
|
|
01ec79b7d5 | ||
|
|
3d6339e2dd | ||
|
|
b4d78d865a | ||
|
|
b53cdcff08 | ||
|
|
3cc18c5807 | ||
|
|
5fdce1e49e | ||
|
|
2ee61a040b | ||
|
|
741b5cc0f9 | ||
|
|
aaf0e070dc | ||
|
|
e0e126eda8 | ||
|
|
236daf6b3b | ||
|
|
f1481b1b1f | ||
|
|
f6e7d3ccf1 | ||
|
|
a754a11913 | ||
|
|
4d25e3b6f6 | ||
|
|
1fac280497 | ||
|
|
c886c5e6a7 | ||
|
|
cd7f78b9ca | ||
|
|
a587d3230f | ||
|
|
93e7b45136 | ||
|
|
e024f71ce1 | ||
|
|
50d1be7b4a | ||
|
|
3ec6d8973b | ||
|
|
3ea47d31a9 | ||
|
|
30c8cc4548 | ||
|
|
98586d2dd9 | ||
|
|
c66d8ecca0 | ||
|
|
db535f7e7d | ||
|
|
89f30d8c31 | ||
|
|
8bce432824 | ||
|
|
f3539b2632 | ||
|
|
7a96c212f3 | ||
|
|
2d5dc54e0f | ||
|
|
cef4c262e9 | ||
|
|
34309261a8 | ||
|
|
657eeb40c7 | ||
|
|
b4938c2cc9 | ||
|
|
524fefc5cb | ||
|
|
73f13404bb | ||
|
|
5fc68cca83 | ||
|
|
fcb7b8fa8d | ||
|
|
a81e32ff95 | ||
|
|
c897052107 | ||
|
|
4d0efc7ea5 | ||
|
|
a77842c9e3 | ||
|
|
0ded9db780 | ||
|
|
58d480fcb5 | ||
|
|
7154428d26 | ||
|
|
02a52d0169 | ||
|
|
ec60764072 | ||
|
|
17d2fa5532 | ||
|
|
3ca8b534e8 | ||
|
|
e88f7c5e46 | ||
|
|
3ac5d7bd1f | ||
|
|
19cad00a00 | ||
|
|
1ea716a163 | ||
|
|
4551659c2a | ||
|
|
c568bf8c24 | ||
|
|
a412d91105 | ||
|
|
ad01f23bbe | ||
|
|
656070b132 | ||
|
|
c907f5ca36 | ||
|
|
94a13b2b54 | ||
|
|
169f11d9c7 | ||
|
|
816dc3af95 | ||
|
|
b802863de5 | ||
|
|
8dc2a4b76c | ||
|
|
beb1c6dbf2 | ||
|
|
3eb06916c7 | ||
|
|
30d0f84a4f | ||
|
|
251f2aa5db | ||
|
|
16d92ddb7c | ||
|
|
e9d4fd8482 | ||
|
|
9fdcf5ab13 | ||
|
|
a6e6de33c8 | ||
|
|
53c7fd4d81 | ||
|
|
2608ea4367 | ||
|
|
d20ed4ad01 | ||
|
|
eae2936449 | ||
|
|
da8ad1f6ba | ||
|
|
5472347969 | ||
|
|
a43f7a6926 | ||
|
|
47f917de0b | ||
|
|
dbeb7edd7a | ||
|
|
6cede712f5 | ||
|
|
d328739192 | ||
|
|
d15c0bd962 | ||
|
|
d53c794936 | ||
|
|
e5dcb25a4a | ||
|
|
f9002b4f43 | ||
|
|
0531508a75 | ||
|
|
174e85a20a | ||
|
|
e9abb6f1e2 | ||
|
|
5be44f9596 | ||
|
|
90efaaff92 | ||
|
|
99b554e7c3 | ||
|
|
ac0ce8a6f6 | ||
|
|
9f9da885b7 | ||
|
|
f51fd2bf3e | ||
|
|
c8378fd32d | ||
|
|
d767214f10 | ||
|
|
e027c504ae | ||
|
|
669a95d975 | ||
|
|
a96df5d518 | ||
|
|
c5c81735a0 | ||
|
|
c933745707 | ||
|
|
6710fd18b3 | ||
|
|
1b78a71529 | ||
|
|
1909d3c94e | ||
|
|
10a107716c | ||
|
|
5445e6637b | ||
|
|
2380cd5cfb | ||
|
|
42661618dc | ||
|
|
8a7e901d7a | ||
|
|
f8bea71716 | ||
|
|
efcc0b7efc | ||
|
|
4d17e913b5 | ||
|
|
b341934863 | ||
|
|
127f8ed3bb | ||
|
|
9dca80de18 | ||
|
|
b0a6bcc055 | ||
|
|
f0626e2eca | ||
|
|
979ab42c5a | ||
|
|
e152e84ddc | ||
|
|
2605978889 | ||
|
|
a2d30efaab | ||
|
|
d3715d00ae | ||
|
|
c37662dbe2 | ||
|
|
768ca1e73d | ||
|
|
a20516850c | ||
|
|
7a203868ec | ||
|
|
43c3ce9a6e | ||
|
|
369e08844f | ||
|
|
48a5452a65 | ||
|
|
c2f9340db6 | ||
|
|
a6508e642a | ||
|
|
a3e73797c2 | ||
|
|
df7bf204ea | ||
|
|
e16243ff55 | ||
|
|
e23cbe67ad | ||
|
|
7702f2f74c | ||
|
|
b457ef9a3f | ||
|
|
a28ed6a977 | ||
|
|
0c73682cfc | ||
|
|
7e380b6cfb | ||
|
|
63b5257986 | ||
|
|
acc4485e81 | ||
|
|
2c44999a31 | ||
|
|
e59a48f9f4 | ||
|
|
b86356b0cc | ||
|
|
f6d7552b58 | ||
|
|
5bd88ac2e9 | ||
|
|
94fa5b59f3 | ||
|
|
7169480999 | ||
|
|
c44ce0d77c | ||
|
|
7fd71a8408 | ||
|
|
68db721089 | ||
|
|
a180e12bdf | ||
|
|
f3a4a1b1be | ||
|
|
6977a8ecbc | ||
|
|
d00f64e6ad | ||
|
|
d5b3da6ba3 | ||
|
|
c245318339 | ||
|
|
b3b0fec2e1 | ||
|
|
9d571a4c71 | ||
|
|
f283858490 | ||
|
|
76fe203767 | ||
|
|
b9a47f2f50 | ||
|
|
27cb17c640 | ||
|
|
ef8fb89eb3 | ||
|
|
f1b045f8a8 | ||
|
|
050066132b | ||
|
|
2a6e6a1e24 | ||
|
|
92689d084c | ||
|
|
00f314039d | ||
|
|
fcb75e837d | ||
|
|
9fbea76b74 | ||
|
|
b3ff120bcf | ||
|
|
9dea98f020 |
308
.github/workflows/deploy.yml
vendored
308
.github/workflows/deploy.yml
vendored
@@ -10,16 +10,18 @@ env:
|
||||
|
||||
jobs:
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QT_VERSION: 6.9.2
|
||||
QIF_VERSION: 4.7
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install Qt'
|
||||
@@ -42,6 +44,13 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -53,13 +62,13 @@ jobs:
|
||||
bash deploy/build_linux.sh
|
||||
|
||||
- name: 'Pack installer'
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
|
||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Linux_installer.tar
|
||||
path: deploy/AmneziaVPN_Linux_Installer.tar
|
||||
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -82,7 +91,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QT_VERSION: 6.9.2
|
||||
QIF_VERSION: 4.7
|
||||
BUILD_ARCH: 64
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
@@ -90,6 +99,8 @@ jobs:
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
@@ -98,6 +109,14 @@ jobs:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -120,19 +139,47 @@ jobs:
|
||||
with:
|
||||
arch: 'x64'
|
||||
|
||||
- name: 'Setup .NET SDK'
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: 'Install WiX Toolset'
|
||||
shell: powershell
|
||||
run: |
|
||||
dotnet tool install --global wix --version 4.0.6
|
||||
wix extension add -g WixToolset.UI.wixext/4.0.6
|
||||
wix extension add -g WixToolset.Util.wixext/4.0.6
|
||||
wix extension list -g
|
||||
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
|
||||
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
run: |
|
||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
|
||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
|
||||
call deploy\\build_windows.bat
|
||||
|
||||
- name: 'Rename Windows installer'
|
||||
shell: cmd
|
||||
run: |
|
||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
|
||||
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload MSI installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_Windows_MSI_installer
|
||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -145,10 +192,10 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
QT_VERSION: 6.9.2
|
||||
CC: cc
|
||||
CXX: c++
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
@@ -156,12 +203,14 @@ jobs:
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.2'
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -190,7 +239,7 @@ jobs:
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.1'
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
@@ -243,18 +292,33 @@ jobs:
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
Build-MacOS-old:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||
QT_VERSION: 6.4.3
|
||||
QIF_VERSION: 4.6
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
@@ -275,11 +339,6 @@ jobs:
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||
run: |
|
||||
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
@@ -293,16 +352,181 @@ jobs:
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
|
||||
bash deploy/build_macos.sh
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_installer
|
||||
path: AmneziaVPN.dmg
|
||||
name: AmneziaVPN_MacOS_old_installer
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_old_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.9.2
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
#setup-python: 'true'
|
||||
#set-env: 'true'
|
||||
#extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
cache: 'true'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos.sh -n
|
||||
|
||||
- name: 'Pack macOS installer'
|
||||
run: |
|
||||
cd deploy/build/pkg
|
||||
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
|
||||
cd ../../..
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.9.2
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
run: |
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos_ne.sh
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -316,14 +540,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
ANDROID_BUILD_PLATFORM: android-34
|
||||
QT_VERSION: 6.7.3
|
||||
ANDROID_BUILD_PLATFORM: android-36
|
||||
QT_VERSION: 6.10.1
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
@@ -396,6 +622,13 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Get version from CMakeLists.txt'
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
@@ -429,35 +662,44 @@ jobs:
|
||||
shell: bash
|
||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
||||
|
||||
- name: 'Rename Android APKs'
|
||||
run: |
|
||||
cd deploy/build
|
||||
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
|
||||
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
|
||||
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
|
||||
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
|
||||
cd ../..
|
||||
|
||||
- name: 'Upload x86_64 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86_64
|
||||
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-x86
|
||||
path: deploy/build/AmneziaVPN-x86-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-arm64-v8a
|
||||
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a apk'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN-android-armeabi-v7a
|
||||
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||
compression-level: 0
|
||||
retention-days: 7
|
||||
|
||||
|
||||
2
.github/workflows/tag-deploy.yml
vendored
2
.github/workflows/tag-deploy.yml
vendored
@@ -20,6 +20,8 @@ jobs:
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
|
||||
61
.github/workflows/tag-upload.yml
vendored
61
.github/workflows/tag-upload.yml
vendored
@@ -1,64 +1,41 @@
|
||||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
RELEASE_VERSION:
|
||||
description: 'Release version (e.g. 1.2.3.4)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
Upload-S3:
|
||||
runs-on: ubuntu-latest
|
||||
name: upload
|
||||
steps:
|
||||
- name: Checkout CMakeLists.txt
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
ref: ${{ inputs.RELEASE_VERSION }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
deploy/deploy_s3.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
run: |
|
||||
GIT_TAG=${{ github.ref_name }}
|
||||
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||
|
||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
||||
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||
else
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
||||
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
||||
out-file-path: ${{ github.ref_name }}
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload beta version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'dev')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: beta/${{ github.ref_name }}
|
||||
|
||||
- name: Upload stable version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'master')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: stable/${{ github.ref_name }}
|
||||
- name: Send dist to S3
|
||||
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -9,6 +9,7 @@ deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
|
||||
# Qt-es
|
||||
@@ -133,4 +134,9 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
|
||||
out/
|
||||
|
||||
# CMake files
|
||||
CMakeFiles/
|
||||
CMakeFiles/
|
||||
|
||||
ios-ne-build.sh
|
||||
macos-ne-build.sh
|
||||
macos-signed-build.sh
|
||||
macos-with-sign-build.sh
|
||||
|
||||
1
.gitmodules
vendored
1
.gitmodules
vendored
@@ -7,6 +7,7 @@
|
||||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
branch = feature/special-handshake
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.12.5)
|
||||
|
||||
project(${PROJECT} VERSION 4.8.4.4
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2081)
|
||||
set(APP_ANDROID_VERSION_CODE 2101)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -31,14 +32,53 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
if(APPLE)
|
||||
if(IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||
elseif(MACOS_NE)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
|
||||
else()
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
endif()
|
||||
|
||||
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
|
||||
|
||||
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
|
||||
|
||||
set(CPACK_GENERATOR "WIX")
|
||||
set(CPACK_WIX_VERSION 4)
|
||||
set(CPACK_PACKAGE_NAME "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
||||
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
||||
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
|
||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
|
||||
|
||||
# WiX patches
|
||||
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
|
||||
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
|
||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
|
||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
|
||||
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
|
||||
|
||||
# WiX v4 Util extension for CloseApplication + namespace for util
|
||||
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
|
||||
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
|
||||
|
||||
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
10
README.md
10
README.md
@@ -9,17 +9,17 @@
|
||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||
|
||||
|
||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
|
||||
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
|
||||
24
README_RU.md
24
README_RU.md
@@ -6,16 +6,16 @@
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
|
||||
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
@@ -30,7 +30,7 @@
|
||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||
- Поддерживает платформы: Windows, MacOS, Linux, Android, iOS.
|
||||
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Ссылки
|
||||
@@ -38,10 +38,10 @@
|
||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддржки в Telegram (Английский)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддржки в Telegram (Фарси)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддржки в Telegram (Мьянма)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддржки в Telegram (Русский)
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||
|
||||
## Технологии
|
||||
@@ -80,8 +80,8 @@ git submodule update --init --recursive
|
||||
Проверьте папку deploy для скриптов сборки.
|
||||
|
||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
|
||||
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Модуль совместимости с Qt 5
|
||||
@@ -117,7 +117,7 @@ $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
```
|
||||
|
||||
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
|
||||
Если сборка завершится с ошибкой:
|
||||
```
|
||||
|
||||
Submodule client/3rd-prebuilt updated: efad1a5b5c...adf5eb920f
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
Submodule client/3rd/amneziawg-apple updated: 76e7db556a...cf63135331
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
set(PROJECT AmneziaVPN)
|
||||
project(${PROJECT})
|
||||
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
|
||||
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
|
||||
@@ -31,9 +30,8 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||
|
||||
if(IOS)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
endif()
|
||||
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
@@ -48,18 +46,17 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||
target_include_directories(${PROJECT} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||
)
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||
@@ -108,6 +105,9 @@ endif()
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||
|
||||
# Add webview module
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/core/webview)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||
@@ -115,6 +115,15 @@ include_directories(
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
if(MACOS_NE)
|
||||
message("MACOS_NE is ON")
|
||||
add_definitions(-DQ_OS_MAC)
|
||||
add_definitions(-DMACOS_NE)
|
||||
message("Add macros for MacOS Network Extension")
|
||||
else()
|
||||
message("MACOS_NE is OFF")
|
||||
endif()
|
||||
|
||||
include_directories(mozilla)
|
||||
include_directories(mozilla/shared)
|
||||
include_directories(mozilla/models)
|
||||
@@ -144,7 +153,7 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
cmake_policy(SET CMP0099 OLD)
|
||||
cmake_policy(SET CMP0099 NEW)
|
||||
cmake_policy(SET CMP0114 NEW)
|
||||
|
||||
if(NOT BUILD_OSX_APP_IDENTIFIER)
|
||||
@@ -163,7 +172,6 @@ if(APPLE)
|
||||
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||
|
||||
endif()
|
||||
|
||||
if(LINUX AND NOT ANDROID)
|
||||
@@ -171,8 +179,7 @@ if(LINUX AND NOT ANDROID)
|
||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
endif()
|
||||
|
||||
@@ -183,12 +190,14 @@ endif()
|
||||
if(IOS)
|
||||
include(cmake/ios.cmake)
|
||||
include(cmake/ios-arch-fixup.cmake)
|
||||
elseif(APPLE AND NOT IOS)
|
||||
elseif(APPLE AND MACOS_NE)
|
||||
include(cmake/macos_ne.cmake)
|
||||
elseif(APPLE)
|
||||
include(cmake/osxtools.cmake)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS} webview)
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
# deploy artifacts required to run the application to the debug build folder
|
||||
@@ -204,7 +213,7 @@ elseif(APPLE AND NOT IOS)
|
||||
set(DEPLOY_PLATFORM_PATH "macos")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
@@ -219,7 +228,6 @@ if(NOT IOS AND NOT ANDROID)
|
||||
$<TARGET_FILE_DIR:${PROJECT}>
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QEvent>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QQmlExtensionPlugin>
|
||||
#include <QtPlugin>
|
||||
#include "core/webview/plugin.h"
|
||||
|
||||
Q_IMPORT_PLUGIN(WebViewPlugin)
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
@@ -21,9 +29,18 @@
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
|
||||
#include "protocols/qml_register_protocols.h"
|
||||
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
||||
#include <QWindow> // for qobject_cast<QWindow*>
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
bool AmneziaApplication::m_forceQuit = false;
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
|
||||
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
|
||||
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
|
||||
{
|
||||
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
||||
// Fix config file permissions
|
||||
@@ -48,8 +65,21 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_vpnConnection) {
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
|
||||
QThread::msleep(2000);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_vpnConnectionThread.requestInterruption();
|
||||
m_vpnConnectionThread.quit();
|
||||
m_vpnConnectionThread.wait(3000);
|
||||
|
||||
if (!m_vpnConnectionThread.wait(3000)) {
|
||||
m_vpnConnectionThread.terminate();
|
||||
m_vpnConnectionThread.wait(500);
|
||||
}
|
||||
|
||||
if (m_engine) {
|
||||
QObject::disconnect(m_engine, 0, 0, 0);
|
||||
@@ -57,21 +87,55 @@ AmneziaApplication::~AmneziaApplication()
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
namespace {
|
||||
static void clearQtCaches()
|
||||
{
|
||||
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
||||
if (!cacheRoot.isEmpty()) {
|
||||
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
|
||||
QDir(cacheRoot + "/qmlcache").removeRecursively();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void AmneziaApplication::init()
|
||||
{
|
||||
m_engine = new QQmlApplicationEngine;
|
||||
|
||||
// Register AmneziaWebView plugin explicitly
|
||||
QObject *pluginInstance = qt_static_plugin_WebViewPlugin().instance();
|
||||
QQmlExtensionPlugin *p = qobject_cast<QQmlExtensionPlugin*>(pluginInstance);
|
||||
if (p) {
|
||||
p->registerTypes("AmneziaWebView");
|
||||
p->initializeEngine(m_engine, "AmneziaWebView");
|
||||
}
|
||||
|
||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||
QObject::connect(
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||
[this, url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl) {
|
||||
QCoreApplication::exit(-1);
|
||||
return;
|
||||
}
|
||||
// install filter on main window
|
||||
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||
win->installEventFilter(this);
|
||||
win->show();
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||
|
||||
#ifdef MACOS_NE
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
|
||||
#else
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
|
||||
#endif
|
||||
|
||||
m_vpnConnection.reset(new VpnConnection(m_settings));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
@@ -79,6 +143,17 @@ void AmneziaApplication::init()
|
||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
m_engine->addImportPath("qrc:/");
|
||||
|
||||
if (m_parser.isSet(m_optImport)) {
|
||||
const QString data = m_parser.value(m_optImport);
|
||||
if (!data.isEmpty()) {
|
||||
if (m_coreController) {
|
||||
m_coreController->importConfigFromData(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_engine->load(url);
|
||||
|
||||
m_coreController->setQmlRoot();
|
||||
@@ -94,7 +169,7 @@ void AmneziaApplication::init()
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN //TODO
|
||||
if (m_parser.isSet("a"))
|
||||
if (m_parser.isSet(m_optAutostart))
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
else
|
||||
emit m_coreController->pageController()->raiseMainWindow();
|
||||
@@ -118,6 +193,18 @@ void AmneziaApplication::init()
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
if (m_parser.isSet(m_optConnect)) {
|
||||
bool ok = false;
|
||||
int idx = m_parser.value(m_optConnect).toInt(&ok);
|
||||
if (ok) {
|
||||
QTimer::singleShot(0, this, [this, idx]() {
|
||||
if (m_coreController) {
|
||||
m_coreController->openConnectionByIndex(idx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaApplication::registerTypes()
|
||||
@@ -162,15 +249,14 @@ bool AmneziaApplication::parseCommands()
|
||||
m_parser.addHelpOption();
|
||||
m_parser.addVersionOption();
|
||||
|
||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
||||
m_parser.addOption(c_autostart);
|
||||
|
||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
||||
m_parser.addOption(c_cleanup);
|
||||
|
||||
m_parser.addOption(m_optAutostart);
|
||||
m_parser.addOption(m_optCleanup);
|
||||
m_parser.addOption(m_optConnect);
|
||||
m_parser.addOption(m_optImport);
|
||||
|
||||
m_parser.process(*this);
|
||||
|
||||
if (m_parser.isSet(c_cleanup)) {
|
||||
if (m_parser.isSet(m_optCleanup)) {
|
||||
Logger::cleanUp();
|
||||
QTimer::singleShot(100, this, [this] { quit(); });
|
||||
exec();
|
||||
@@ -179,9 +265,8 @@ bool AmneziaApplication::parseCommands()
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::startLocalServer()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
@@ -198,6 +283,32 @@ void AmneziaApplication::startLocalServer()
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Close) {
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
quit();
|
||||
#else
|
||||
if (m_forceQuit) {
|
||||
quit();
|
||||
} else {
|
||||
if (m_coreController && m_coreController->pageController()) {
|
||||
m_coreController->pageController()->hideMainWindow();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true; // eat the close
|
||||
}
|
||||
// call base QObject::eventFilter
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void AmneziaApplication::forceQuit()
|
||||
{
|
||||
m_forceQuit = true;
|
||||
quit();
|
||||
}
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
#include <QApplication>
|
||||
#endif
|
||||
#include <QClipboard>
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||
#else
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#define AMNEZIA_BASE_CLASS QApplication
|
||||
#endif
|
||||
|
||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
void loadFonts();
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
void startLocalServer();
|
||||
#endif
|
||||
|
||||
@@ -45,7 +45,11 @@ public:
|
||||
QNetworkAccessManager *networkManager();
|
||||
QClipboard *getClipboard();
|
||||
|
||||
public slots:
|
||||
void forceQuit();
|
||||
|
||||
private:
|
||||
static bool m_forceQuit;
|
||||
QQmlApplicationEngine *m_engine {};
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
@@ -56,10 +60,17 @@ private:
|
||||
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QCommandLineOption m_optAutostart;
|
||||
QCommandLineOption m_optCleanup;
|
||||
QCommandLineOption m_optConnect;
|
||||
QCommandLineOption m_optImport;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||
android:windowSoftInputMode="adjustResize|stateUnchanged"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
@@ -214,4 +215,4 @@
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
|
||||
openVpnClient = null
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
openVpnClient?.let {
|
||||
it.establish = makeEstablish(vpnBuilder)
|
||||
it.reconnect(0)
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class Protocol {
|
||||
|
||||
abstract fun stopVpn()
|
||||
|
||||
abstract fun reconnectVpn(vpnBuilder: Builder)
|
||||
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||
|
||||
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
|
||||
if (!allowSplitTunneling) {
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<item name="android:colorBackground">@color/black</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:enforceNavigationBarContrast">false</item>
|
||||
<item name="android:enforceStatusBarContrast">false</item>
|
||||
</style>
|
||||
<style name="Translucent" parent="NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
@@ -35,6 +35,11 @@ import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -170,10 +175,9 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "Create Amnezia activity")
|
||||
loadLibs()
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
|
||||
// Configure window for edge-to-edge display
|
||||
configureWindowForEdgeToEdge()
|
||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val proto = mainScope.async(Dispatchers.IO) {
|
||||
VpnStateStore.getVpnState().vpnProto
|
||||
@@ -265,6 +269,98 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Log.d(TAG, "Pause Amnezia activity")
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.decorView.apply {
|
||||
invalidate()
|
||||
|
||||
postDelayed({
|
||||
sendTouch(1f, 1f)
|
||||
}, 100)
|
||||
|
||||
postDelayed({
|
||||
sendTouch(2f, 2f)
|
||||
}, 200)
|
||||
|
||||
postDelayed({
|
||||
requestLayout()
|
||||
invalidate()
|
||||
}, 250)
|
||||
}
|
||||
} */
|
||||
Log.d(TAG, "Resume Amnezia activity")
|
||||
}
|
||||
|
||||
private fun configureWindowForEdgeToEdge() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
||||
statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
navigationBarColor = android.graphics.Color.TRANSPARENT
|
||||
}
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = false
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
|
||||
// Workaround for Android 14 (API 34+) IME adjustResize bug
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
setupImeInsetsListener()
|
||||
}
|
||||
} else {
|
||||
window.apply {
|
||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
statusBarColor = getColor(R.color.black)
|
||||
}
|
||||
|
||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = false
|
||||
isAppearanceLightNavigationBars = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupImeInsetsListener() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
||||
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
||||
|
||||
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
val imeHeightDp = (imeHeight / density).toInt()
|
||||
|
||||
// Also track system bars (navigation bar, status bar) changes
|
||||
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val navBarHeight = systemBarsInsets.bottom
|
||||
val navBarHeightDp = (navBarHeight / density).toInt()
|
||||
val statusBarHeight = systemBarsInsets.top
|
||||
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
||||
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
||||
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
||||
}
|
||||
|
||||
// Return windowInsets instead of CONSUMED to allow proper handling
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy Amnezia activity")
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
@@ -666,6 +762,43 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
|
||||
@Suppress("unused")
|
||||
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
|
||||
@Suppress("unused")
|
||||
fun getStatusBarHeight(): Int {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||
|
||||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||
val heightPx = if (resourceId > 0) {
|
||||
resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
// Convert physical pixels to device-independent pixels for QML
|
||||
val density = resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getNavigationBarHeight(): Int {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
||||
|
||||
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
||||
val heightPx = if (resourceId > 0) {
|
||||
resources.getDimensionPixelSize(resourceId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
// Convert physical pixels to device-independent pixels for QML
|
||||
val density = resources.displayMetrics.density
|
||||
val heightDp = (heightPx / density).toInt()
|
||||
return heightDp
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun startQrCodeReader() {
|
||||
Log.v(TAG, "Start camera")
|
||||
|
||||
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
|
||||
protocolState.value = RECONNECTING
|
||||
|
||||
connectionJob = connectionScope.launch {
|
||||
vpnProto?.protocol?.reconnectVpn(Builder())
|
||||
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,15 +38,15 @@ object AppListProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
|
||||
val name: String?
|
||||
val packageName: String = pi.packageName
|
||||
val icon: Boolean = ai.icon != 0
|
||||
val icon: Boolean = (ai?.icon ?: 0) != 0
|
||||
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||
|
||||
init {
|
||||
val name = ai.loadLabel(pm).toString()
|
||||
this.name = if (name != packageName) name else null
|
||||
val name = ai?.loadLabel(pm)?.toString()
|
||||
this.name = name?.takeIf { it != packageName }
|
||||
}
|
||||
|
||||
override fun compareTo(other: App): Int {
|
||||
|
||||
609
client/android/src/org/amnezia/vpn/WebViewController.java
Normal file
609
client/android/src/org/amnezia/vpn/WebViewController.java
Normal file
@@ -0,0 +1,609 @@
|
||||
package org.amnezia.vpn;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.MotionEvent;
|
||||
import android.webkit.*;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Message;
|
||||
import org.qtproject.qt.android.WebViewControllerEx;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
public class WebViewController
|
||||
{
|
||||
private interface RequestFinished {
|
||||
void onRequestCompleted();
|
||||
}
|
||||
|
||||
private String baseUrl = "";
|
||||
private static final String INTERNAL_BASE_URL = "file:///";
|
||||
private static final long GEOMETRY_STABLE_INTERVAL = 150; //ms wait geometry settle
|
||||
|
||||
private final Activity m_activity;
|
||||
private final long m_id;
|
||||
private WebView m_webView = null;
|
||||
private ViewGroup m_layout = null;
|
||||
private boolean m_loading = true;
|
||||
private long mLastGeometryChange = 0L;
|
||||
|
||||
private float m_displayDensity = (float) 1.0;
|
||||
|
||||
public native void urlChanged(long viewId, String url);
|
||||
public native byte[] dataForUrl(long viewId, String url, StringBuilder mimeType, StringBuilder encoding);
|
||||
public native boolean canHandleUrl(long viewId, String url);
|
||||
private final Handler m_handler;
|
||||
|
||||
private native void pageFinished(long id, String url);
|
||||
private native void pageStarted(long id, String url);
|
||||
private static final String TAG = WebViewController.class.getSimpleName();
|
||||
|
||||
private class AndroidWebChromeClient extends WebChromeClient {
|
||||
@Override
|
||||
public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, Message resultMsg)
|
||||
{
|
||||
// Prevent opening new windows/tabs - load URLs in the same WebView instead
|
||||
// This handles links with target="_blank" or window.open()
|
||||
// Return false to prevent creating new windows - URLs will be handled by shouldOverrideUrlLoading
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public WebViewController(final Activity activity, final long id) {
|
||||
m_activity = activity;
|
||||
m_id = id;
|
||||
|
||||
ViewGroup root = (ViewGroup)(((ViewGroup)(m_activity.findViewById(android.R.id.content))).getChildAt(0));
|
||||
if (root != null) {
|
||||
m_layout = root;
|
||||
}
|
||||
|
||||
m_displayDensity = m_activity.getResources().getDisplayMetrics().density;
|
||||
|
||||
m_handler = new Handler(Looper.getMainLooper());
|
||||
m_handler.post(new Runnable() {
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
public void run() {
|
||||
m_webView = new WebView(m_activity);
|
||||
m_webView.setFocusable(true);
|
||||
|
||||
m_webView.setFocusableInTouchMode(true);
|
||||
m_webView.getSettings().setJavaScriptEnabled(true);
|
||||
m_webView.getSettings().setAllowFileAccess(true);
|
||||
m_webView.getSettings().setAllowFileAccessFromFileURLs(true);
|
||||
m_webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
|
||||
m_webView.getSettings().setAllowContentAccess(true);
|
||||
m_webView.getSettings().setBuiltInZoomControls(true);
|
||||
m_webView.getSettings().setDisplayZoomControls(false);
|
||||
m_webView.getSettings().setLoadWithOverviewMode(true);
|
||||
|
||||
m_webView.getSettings().setSupportMultipleWindows(false); // Prevent opening new windows
|
||||
m_webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
|
||||
m_webView.getSettings().setUseWideViewPort(true);
|
||||
m_webView.getSettings().setLoadWithOverviewMode(true);
|
||||
m_webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
|
||||
|
||||
m_webView.getSettings().setSupportZoom(true);
|
||||
m_webView.getSettings().setBuiltInZoomControls(true);
|
||||
m_webView.getSettings().setDisplayZoomControls(false);
|
||||
|
||||
m_webView.setInitialScale(0);
|
||||
m_webView.setVisibility(android.view.View.INVISIBLE);
|
||||
|
||||
// Ensure WebView can receive and handle touch events for link clicks
|
||||
m_webView.setClickable(true);
|
||||
m_webView.setLongClickable(true);
|
||||
m_webView.setHapticFeedbackEnabled(false);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
m_webView.setElevation(0f);
|
||||
}
|
||||
|
||||
m_webView.setWebViewClient(buildWebViewClient());
|
||||
m_webView.setWebChromeClient(buildWebChromeClient());
|
||||
|
||||
// Ensure IME appears on tap when focusing editable content
|
||||
m_webView.setOnTouchListener(new android.view.View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(android.view.View v, MotionEvent event) {
|
||||
// Let WebView handle touch events normally for link clicks
|
||||
// Only request focus on ACTION_UP to allow IME to appear for input fields
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
v.requestFocus();
|
||||
// Do not show IME if app temporarily suppresses it
|
||||
try {
|
||||
android.view.inputmethod.InputMethodManager imm = (android.view.inputmethod.InputMethodManager) v.getContext().getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(v, 0);
|
||||
}
|
||||
} catch (Throwable ignore) {}
|
||||
}
|
||||
// Return false to let WebView handle the touch event (for link clicks, etc.)
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private WebViewClient buildWebViewClient() {
|
||||
return new WebViewClient() {
|
||||
@Override
|
||||
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
|
||||
handler.proceed();
|
||||
Log.e(TAG, "SSL certificate error");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted (WebView view, String url, Bitmap favicon) {
|
||||
m_loading = true;
|
||||
|
||||
String dataUrl = updateUrl(url);
|
||||
urlChanged(m_id, dataUrl);
|
||||
pageStarted(m_id, dataUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
m_loading = false;
|
||||
|
||||
String dataUrl = updateUrl(url);
|
||||
pageFinished(m_id, dataUrl);
|
||||
urlChanged(m_id, dataUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
Log.d(TAG, "shouldOverrideUrlLoading (deprecated): " + url);
|
||||
if (url == null || url.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
urlChanged(m_id, url);
|
||||
|
||||
// Always load URLs within WebView, don't open in external browser
|
||||
// Explicitly load the URL in the WebView and return true to indicate we handled it
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, android.webkit.WebResourceRequest request) {
|
||||
String url = request.getUrl().toString();
|
||||
Log.d(TAG, "shouldOverrideUrlLoading (new): " + url + ", isMainFrame: " + request.isForMainFrame() + ", method: " + request.getMethod());
|
||||
|
||||
if (url == null || url.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
urlChanged(m_id, url);
|
||||
|
||||
// Always load URLs within WebView, don't open in external browser
|
||||
// Handle main frame navigation (link clicks, form submissions, etc.)
|
||||
// For sub-resources (images, CSS, JS), let WebView handle normally by returning false
|
||||
if (request.isForMainFrame()) {
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
// For sub-resources, let WebView handle normally
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
|
||||
if (url.startsWith("data:") || !canHandleUrl(m_id, url)) {
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
|
||||
StringBuilder mimeType = new StringBuilder();
|
||||
StringBuilder encoding = new StringBuilder();
|
||||
byte[] data = dataForUrl(m_id, url, mimeType, encoding);
|
||||
|
||||
boolean isDataInvalid = (data == null) || (data.length == 0);
|
||||
if (isDataInvalid) {
|
||||
Log.w(TAG, String.format("Invalid data received for url: %s", url));
|
||||
return null;
|
||||
}
|
||||
if ((mimeType.length() == 0) || mimeType.toString().isEmpty()) {
|
||||
Log.w(TAG, String.format("Invalid mimeType received for url: %s", url));
|
||||
}
|
||||
if ((encoding.length() == 0) || encoding.toString().isEmpty()) {
|
||||
Log.w(TAG, String.format("Invalid encoding received for url: %s", url));
|
||||
}
|
||||
|
||||
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
|
||||
return new WebResourceResponse(mimeType.toString(), encoding.toString(), dataStream);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private WebChromeClient buildWebChromeClient() {
|
||||
return new AndroidWebChromeClient() {
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
super.onProgressChanged(view, newProgress);
|
||||
if (newProgress == 100) {
|
||||
m_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
|
||||
callback.invoke(origin, true, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String updateUrl(String url) {
|
||||
if (!url.startsWith(INTERNAL_BASE_URL)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
String dataUrl = url;
|
||||
|
||||
try {
|
||||
dataUrl = URLDecoder.decode(url.substring(INTERNAL_BASE_URL.length()), "UTF-8");
|
||||
if ((dataUrl.length() == 1) && dataUrl.endsWith("#")) {
|
||||
dataUrl = baseUrl;
|
||||
} else {
|
||||
dataUrl = baseUrl + dataUrl;
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
|
||||
return dataUrl;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(() -> {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.setVisibility(android.view.View.INVISIBLE);
|
||||
m_layout.removeView(m_webView);
|
||||
m_webView.stopLoading();
|
||||
m_webView.setWebViewClient(new WebViewClient());
|
||||
m_webView.setWebChromeClient(null);
|
||||
m_webView = null;
|
||||
});
|
||||
}
|
||||
|
||||
public void setGeometry(final int x, final int y, final int width, final int height) {
|
||||
|
||||
|
||||
Log.d(TAG, String.format(
|
||||
"setGeometry called: x=%d, y=%d, width=%d, height=%d",
|
||||
x, y, width, height));
|
||||
|
||||
|
||||
if (m_handler == null)
|
||||
return;
|
||||
|
||||
m_handler.post(() -> {
|
||||
if (m_webView == null || m_layout == null)
|
||||
return;
|
||||
|
||||
if (m_layout.indexOfChild(m_webView) < 0) {
|
||||
m_layout.addView(m_webView);
|
||||
}
|
||||
|
||||
float scale = m_activity.getResources().getDisplayMetrics().density;
|
||||
|
||||
int pxX = Math.round(x * scale);
|
||||
int pxY = Math.round(y * scale);
|
||||
int pxW = Math.round(width * scale);
|
||||
int pxH = Math.round(height * scale);
|
||||
|
||||
Log.d(TAG, String.format(
|
||||
"density=%.2f qml: x=%d y=%d w=%d h=%d -> px: x=%d y=%d w=%d h=%d",
|
||||
scale, x, y, width, height, pxX, pxY, pxW, pxH));
|
||||
|
||||
ViewGroup.LayoutParams params =
|
||||
WebViewControllerEx.createQtLayoutParams(
|
||||
pxW,
|
||||
pxH,
|
||||
pxX,
|
||||
pxY
|
||||
);
|
||||
|
||||
m_webView.setLayoutParams(params);
|
||||
m_webView.setInitialScale(0);
|
||||
|
||||
Log.d(TAG, String.format(
|
||||
"WebView positioned (QtLayout) at px: x=%d, y=%d, w=%d, h=%d",
|
||||
pxX, pxY, pxW, pxH));
|
||||
|
||||
m_layout.requestLayout();
|
||||
m_webView.requestLayout();
|
||||
|
||||
mLastGeometryChange = SystemClock.uptimeMillis();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void show() {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
if (m_webView.getVisibility() != android.view.View.VISIBLE) {
|
||||
m_webView.setVisibility(android.view.View.VISIBLE);
|
||||
}
|
||||
// Don't bring WebView to front - let QML elements render on top
|
||||
// Set low elevation so QML elements can appear above WebView
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
m_webView.setElevation(0f);
|
||||
}
|
||||
m_webView.requestLayout();
|
||||
m_layout.requestLayout();
|
||||
m_layout.postInvalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
hideWebView();
|
||||
long now = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
private void hideWebView() {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
m_handler.post(() -> {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
if (m_webView.getVisibility() == android.view.View.VISIBLE) {
|
||||
m_webView.setVisibility(android.view.View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadUrl(String url) {
|
||||
final String newUrl;
|
||||
if ((baseUrl.length() > 0) && url.startsWith(baseUrl)) {
|
||||
newUrl = INTERNAL_BASE_URL + url.substring(baseUrl.length());
|
||||
} else {
|
||||
newUrl = url;
|
||||
}
|
||||
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.loadUrl(newUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadDataWithBaseURL(final String url, final String html, final String mime, final String encoding) {
|
||||
baseUrl = url.trim();
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_webView.loadUrl("about:blank");
|
||||
m_webView.loadDataWithBaseURL(INTERNAL_BASE_URL, html, mime, encoding, null);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public void evaluateJavaScript(final String script) {
|
||||
if (m_handler == null || script == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.evaluateJavascript(script, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean canGoBack() {
|
||||
final WebView view = m_webView;
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean[] ret = new boolean[1];
|
||||
ret[0] = false;
|
||||
boolean can = false;
|
||||
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
if (m_activity != null) {
|
||||
m_activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ret[0] = view.canGoBack();
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
semaphore.acquire(1);
|
||||
can = ret[0];
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return can;
|
||||
}
|
||||
|
||||
public void goBack() {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean canGoForward() {
|
||||
final WebView view = m_webView;
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean[] ret = new boolean[1];
|
||||
ret[0] = false;
|
||||
boolean can = false;
|
||||
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
if (m_activity != null) {
|
||||
m_activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ret[0] = view.canGoForward();
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
semaphore.acquire(1);
|
||||
can = ret[0];
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return can;
|
||||
}
|
||||
|
||||
public void goForward() {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.goForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setBackgroundColor(final int color) {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.setBackgroundColor(color);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setTextZoom(final int percent) {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
final int clamped = Math.max(25, Math.min(500, percent));
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (m_webView == null) {
|
||||
return;
|
||||
}
|
||||
m_webView.getSettings().setTextZoom(clamped);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int convertToDp(int input) {
|
||||
return (int)(input / m_displayDensity + 0.5f);
|
||||
}
|
||||
|
||||
public void setDefaultFontSize(int size) {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
final int fontSize = size;
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_webView.getSettings().setDefaultFontSize(convertToDp(fontSize));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void setStandardFontFamily(String family) {
|
||||
if (m_handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String fontFamily = family;
|
||||
m_handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
m_webView.getSettings().setStandardFontFamily(fontFamily);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,4 +28,7 @@ object QtAndroidController {
|
||||
external fun onAuthResult(result: Boolean)
|
||||
|
||||
external fun decodeQrCode(data: String): Boolean
|
||||
|
||||
external fun onImeInsetsChanged(heightDp: Int)
|
||||
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.qtproject.qt.android;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class WebViewControllerEx {
|
||||
|
||||
public static ViewGroup.LayoutParams createQtLayoutParams(int width, int height, int x, int y) {
|
||||
return new QtLayout.LayoutParams(width, height, x, y);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
|
||||
import java.nio.channels.FileLock
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import org.amnezia.vpn.util.Log.Priority.D
|
||||
import org.amnezia.vpn.util.Log.Priority.E
|
||||
@@ -135,8 +137,8 @@ object Log {
|
||||
}
|
||||
|
||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||
val date = LocalDateTime.now().format(dateTimeFormat)
|
||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
|
||||
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||
"$tag: $msg\n"
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnException
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||
import org.amnezia.vpn.util.Log
|
||||
@@ -27,6 +28,7 @@ private const val TAG = "Wireguard"
|
||||
open class Wireguard : Protocol() {
|
||||
|
||||
private var tunnelHandle: Int = -1
|
||||
private var config: WireguardConfig? = null // save config for reconnect
|
||||
protected open val ifName: String = "amn0"
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var statusJob: Job? = null
|
||||
@@ -61,6 +63,7 @@ open class Wireguard : Protocol() {
|
||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val wireguardConfig = parseConfig(config)
|
||||
start(wireguardConfig, vpnBuilder, protect)
|
||||
this.config = wireguardConfig
|
||||
}
|
||||
|
||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||
@@ -120,14 +123,26 @@ open class Wireguard : Protocol() {
|
||||
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
||||
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
||||
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
|
||||
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
|
||||
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
|
||||
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
|
||||
configData.optStringOrNull("I1")?.let { setI1(it) }
|
||||
configData.optStringOrNull("I2")?.let { setI2(it) }
|
||||
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||
}
|
||||
|
||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
if (tunnelHandle != -1) {
|
||||
private fun start(
|
||||
config: WireguardConfig,
|
||||
vpnBuilder: Builder,
|
||||
protect: (Int) -> Boolean,
|
||||
stopExistingVpn: Boolean = false
|
||||
) {
|
||||
if (!stopExistingVpn && tunnelHandle != -1) {
|
||||
Log.w(TAG, "Tunnel already up")
|
||||
return
|
||||
}
|
||||
@@ -135,6 +150,9 @@ open class Wireguard : Protocol() {
|
||||
buildVpnInterface(config, vpnBuilder)
|
||||
|
||||
vpnBuilder.establish().use { tunFd ->
|
||||
if (stopExistingVpn && tunnelHandle != -1) {
|
||||
turnOffVpn()
|
||||
}
|
||||
if (tunFd == null) {
|
||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||
}
|
||||
@@ -191,20 +209,25 @@ open class Wireguard : Protocol() {
|
||||
return lastHandshake
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
if (tunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down")
|
||||
return
|
||||
}
|
||||
private fun turnOffVpn() {
|
||||
statusJob?.cancel()
|
||||
statusJob = null
|
||||
val handleToClose = tunnelHandle
|
||||
tunnelHandle = -1
|
||||
GoBackend.awgTurnOff(handleToClose)
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
if (tunnelHandle == -1) {
|
||||
Log.w(TAG, "Tunnel already down")
|
||||
return
|
||||
}
|
||||
turnOffVpn()
|
||||
state.value = DISCONNECTED
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
state.value = CONNECTED
|
||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val config = this.config ?: throw VpnException("Reconnect config is empty")
|
||||
start(config, vpnBuilder, protect, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,17 @@ open class WireguardConfig protected constructor(
|
||||
val jmax: Int?,
|
||||
val s1: Int?,
|
||||
val s2: Int?,
|
||||
val h1: Long?,
|
||||
val h2: Long?,
|
||||
val h3: Long?,
|
||||
val h4: Long?
|
||||
val s3: Int?,
|
||||
val s4: Int?,
|
||||
val h1: String?,
|
||||
val h2: String?,
|
||||
val h3: String?,
|
||||
val h4: String?,
|
||||
var i1: String?,
|
||||
var i2: String?,
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -39,10 +46,17 @@ open class WireguardConfig protected constructor(
|
||||
builder.jmax,
|
||||
builder.s1,
|
||||
builder.s2,
|
||||
builder.s3,
|
||||
builder.s4,
|
||||
builder.h1,
|
||||
builder.h2,
|
||||
builder.h3,
|
||||
builder.h4
|
||||
builder.h4,
|
||||
builder.i1,
|
||||
builder.i2,
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -61,10 +75,17 @@ open class WireguardConfig protected constructor(
|
||||
appendLine("jmax=$jmax")
|
||||
appendLine("s1=$s1")
|
||||
appendLine("s2=$s2")
|
||||
s3?.let { appendLine("s3=$it") }
|
||||
s4?.let { appendLine("s4=$it") }
|
||||
appendLine("h1=$h1")
|
||||
appendLine("h2=$h2")
|
||||
appendLine("h3=$h3")
|
||||
appendLine("h4=$h4")
|
||||
i1?.let { appendLine("i1=$it") }
|
||||
i2?.let { appendLine("i2=$it") }
|
||||
i3?.let { appendLine("i3=$it") }
|
||||
i4?.let { appendLine("i4=$it") }
|
||||
i5?.let { appendLine("i5=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,10 +138,17 @@ open class WireguardConfig protected constructor(
|
||||
internal var jmax: Int? = null
|
||||
internal var s1: Int? = null
|
||||
internal var s2: Int? = null
|
||||
internal var h1: Long? = null
|
||||
internal var h2: Long? = null
|
||||
internal var h3: Long? = null
|
||||
internal var h4: Long? = null
|
||||
internal var s3: Int? = null
|
||||
internal var s4: Int? = null
|
||||
internal var h1: String? = null
|
||||
internal var h2: String? = null
|
||||
internal var h3: String? = null
|
||||
internal var h4: String? = null
|
||||
internal var i1: String? = null
|
||||
internal var i2: String? = null
|
||||
internal var i3: String? = null
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
@@ -139,10 +167,17 @@ open class WireguardConfig protected constructor(
|
||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
||||
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||
fun setH1(h1: String) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: String) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: String) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: String) = apply { this.h4 = h4 }
|
||||
fun setI1(i1: String) = apply { this.i1 = i1 }
|
||||
fun setI2(i2: String) = apply { this.i2 = i2 }
|
||||
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ class Xray : Protocol() {
|
||||
state.value = DISCONNECTED
|
||||
}
|
||||
|
||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
state.value = CONNECTED
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class Xray : Protocol() {
|
||||
mtu = config.mtu.toLong()
|
||||
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
||||
device = "fd://$fd"
|
||||
logLevel = "warning"
|
||||
logLevel = "warn"
|
||||
}
|
||||
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
||||
throw VpnStartException("Failed to start tun2socks: $err")
|
||||
|
||||
@@ -27,12 +27,18 @@ if(WIN32)
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||
endif()
|
||||
elseif(APPLE AND NOT IOS)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
if(MACOS_NE)
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
|
||||
else()
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||
endif()
|
||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||
elseif(IOS)
|
||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
||||
@@ -56,7 +62,7 @@ elseif(LINUX)
|
||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
|
||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
|
||||
endif()
|
||||
|
||||
|
||||
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
|
||||
DESTINATION ${OPENSSL_LIBRARIES_DIR})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||
|
||||
set(APP_ANDROID_MIN_SDK 26)
|
||||
set(APP_ANDROID_MIN_SDK 28)
|
||||
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||
"The minimum API level supported by the application or library" FORCE)
|
||||
|
||||
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
|
||||
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
||||
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
||||
QT_ANDROID_TARGET_SDK_VERSION 34
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
||||
QT_ANDROID_TARGET_SDK_VERSION 36
|
||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
|
||||
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
||||
)
|
||||
|
||||
@@ -20,7 +20,11 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
|
||||
|
||||
# We need to include qtprivate api's
|
||||
# As QAndroidBinder is not yet implemented with a public api
|
||||
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
|
||||
# Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
|
||||
if(TARGET Qt6::CorePrivate)
|
||||
set(LIBS ${LIBS} Qt6::CorePrivate)
|
||||
endif()
|
||||
set(LIBS ${LIBS} -ljnigraphics)
|
||||
|
||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
@@ -46,6 +47,8 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
)
|
||||
|
||||
|
||||
@@ -76,8 +79,22 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
|
||||
if(DEFINED DEPLOY)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
|
||||
@@ -14,11 +14,15 @@ set(LIBS ${LIBS}
|
||||
${FW_SECURITY}
|
||||
${FW_COREWLAN}
|
||||
${FW_NETWORK}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_USER_NOTIFICATIONS}
|
||||
${FW_NETWORK_EXTENSION}
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
@@ -31,6 +35,8 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
@@ -49,4 +55,3 @@ execute_process(
|
||||
)
|
||||
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
|
||||
|
||||
|
||||
|
||||
170
client/cmake/macos_ne.cmake
Normal file
170
client/cmake/macos_ne.cmake
Normal file
@@ -0,0 +1,170 @@
|
||||
message("Client ==> MacOS NE build")
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
|
||||
# Link Qt Widgets for QWidget, QMenu, QAction etc.
|
||||
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
|
||||
|
||||
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
|
||||
find_library(FW_AVFOUNDATION AVFoundation)
|
||||
find_library(FW_FOUNDATION Foundation)
|
||||
find_library(FW_STOREKIT StoreKit)
|
||||
find_library(FW_SERVICEMGMT ServiceManagement)
|
||||
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
||||
find_library(FW_NETWORKEXTENSION NetworkExtension)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${FW_AUTHENTICATIONSERVICES}
|
||||
${FW_AVFOUNDATION}
|
||||
${FW_FOUNDATION}
|
||||
${FW_STOREKIT}
|
||||
${FW_SERVICEMGMT}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_NETWORKEXTENSION}
|
||||
)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
)
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE
|
||||
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
|
||||
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
|
||||
)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||
)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(macos/networkextension)
|
||||
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
|
||||
|
||||
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||
message("QtCore_location")
|
||||
message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
|
||||
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
|
||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Signing OpenVPNAdapter framework"
|
||||
)
|
||||
@@ -28,6 +28,7 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
@@ -36,10 +37,9 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
@@ -79,6 +79,7 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
@@ -86,15 +87,28 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Include native macOS platform helpers (dock/status-item)
|
||||
if(APPLE AND NOT IOS)
|
||||
list(APPEND HEADERS
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
|
||||
${CLIENT_ROOT_DIR}/ui/macos_util.h
|
||||
)
|
||||
list(APPEND SOURCES
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
|
||||
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
|
||||
${CLIENT_ROOT_DIR}/ui/macos_util.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||
@@ -161,7 +175,7 @@ if(WIN32)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
@@ -175,11 +189,13 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
@@ -189,3 +205,14 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE AND MACOS_NE)
|
||||
# Include only the tray notification handler in NE builds
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "awg_configurator.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -39,6 +40,18 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
|
||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
|
||||
jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
|
||||
}
|
||||
|
||||
jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
|
||||
jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
|
||||
jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
|
||||
jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
|
||||
jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
|
||||
|
||||
jsonConfig[config_key::mtu] =
|
||||
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "core/networkUtilities.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
@@ -82,12 +83,30 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
|
||||
return "";
|
||||
}
|
||||
|
||||
auto sanitizeStaticKey = [](const QString &key) {
|
||||
QStringList lines = key.split('\n');
|
||||
QStringList filtered;
|
||||
filtered.reserve(lines.size());
|
||||
for (const QString &line : lines) {
|
||||
const QString trimmed = line.trimmed();
|
||||
if (trimmed.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
filtered.append(line);
|
||||
}
|
||||
QString result = filtered.join('\n');
|
||||
if (!result.endsWith('\n')) {
|
||||
result.append('\n');
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||
|
||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
||||
} else {
|
||||
config.replace("<tls-auth>", "");
|
||||
config.replace("</tls-auth>", "");
|
||||
@@ -116,23 +135,23 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||
if (!isApiConfig) {
|
||||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||
config.replace(dnsRegex, "");
|
||||
}
|
||||
|
||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||
|
||||
// no redirect-gateway
|
||||
// no redirect-gateway
|
||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
}
|
||||
@@ -166,10 +185,15 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
|
||||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||
config.replace(dnsRegex, "");
|
||||
}
|
||||
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
config.append("block-ipv6\n");
|
||||
|
||||
// remove block-outside-dns for all exported configs
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QTemporaryFile>
|
||||
#include <QThread>
|
||||
#include <qtimer.h>
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
#include <QGuiApplication>
|
||||
#else
|
||||
#include <QApplication>
|
||||
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
|
||||
|
||||
QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||
#endif
|
||||
}
|
||||
|
||||
// DEAD CODE.
|
||||
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QProcess *p = new QProcess();
|
||||
p->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
|
||||
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
|
||||
pathEnvVar.clear();
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||
#elif defined(Q_OS_MACX)
|
||||
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
|
||||
|
||||
@@ -103,7 +103,11 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString configPath = m_serverConfigPath;
|
||||
if (container == DockerContainer::Awg) {
|
||||
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
|
||||
}
|
||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
@@ -161,15 +165,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
"AllowedIPs = %3/32\n\n")
|
||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||
|
||||
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
|
||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
||||
.arg(m_serverConfigPath);
|
||||
bool isAwg = (container == DockerContainer::Awg2);
|
||||
QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
|
||||
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
|
||||
QString script = QString(
|
||||
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
|
||||
|
||||
errorCode = m_serverController->runScript(
|
||||
credentials,
|
||||
|
||||
@@ -28,7 +28,10 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
return "amnezia-awg2";
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||
|
||||
@@ -41,7 +44,10 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
|
||||
return "none";
|
||||
if (c == DockerContainer::Ipsec)
|
||||
return "ikev2";
|
||||
|
||||
if (c == DockerContainer::Awg)
|
||||
return "awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
return "awg";
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||
|
||||
@@ -71,6 +77,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
||||
|
||||
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
||||
|
||||
case DockerContainer::Awg: return { Proto::Awg };
|
||||
case DockerContainer::Awg2: return { Proto::Awg };
|
||||
default: return { defaultProtocol(container) };
|
||||
}
|
||||
}
|
||||
@@ -94,6 +102,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
{ DockerContainer::Xray, "XRay" },
|
||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||
{ DockerContainer::SSXray, "Shadowsocks"},
|
||||
@@ -118,6 +127,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("AmneziaWG Legacy is a outdated version of AmneziaWG protocol. To upgrade, install AmneziaWG and recreate users.") },
|
||||
{ DockerContainer::Awg2,
|
||||
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||
{ DockerContainer::Xray,
|
||||
@@ -140,98 +151,83 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
{
|
||||
return {
|
||||
{ DockerContainer::OpenVpn,
|
||||
QObject::tr(
|
||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
||||
"It employs its unique security protocol, "
|
||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
||||
"catering to a wide range of devices and operating systems. "
|
||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
||||
"which continually reinforces its security. "
|
||||
"With a strong balance of performance, security, and compatibility, "
|
||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Normal power consumption on mobile devices\n"
|
||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||
"* Recognised by DPI systems and therefore susceptible to blocking\n"
|
||||
"* Can operate over both TCP and UDP network protocols.") },
|
||||
QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
|
||||
"It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
|
||||
"and is continuously improved by the community due to its open-source nature. "
|
||||
"It provides a good balance between speed and security but is easily recognized by DPI systems, "
|
||||
"making it susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Normal battery consumption on mobile devices\n"
|
||||
"* Flexible customization for various devices and OS\n"
|
||||
"* Operates over both TCP and UDP protocols") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||
"* Configurable encryption protocol\n"
|
||||
QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
|
||||
"Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
|
||||
"Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available in AmneziaVPN only on desktop platforms\n"
|
||||
"* Customizable encryption protocol\n"
|
||||
"* Detectable by some DPI systems\n"
|
||||
"* Works over TCP network protocol.") },
|
||||
"* Operates over TCP protocol\n") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||
"protecting against detection.\n\n"
|
||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
||||
"and the server.\n\n"
|
||||
"Cloak protects OpenVPN from detection. \n\n"
|
||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||
"being detected\n\n"
|
||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||
"invisible to analysis systems.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
|
||||
"\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
|
||||
"\nThe Cloak plugin further protects the connection from DPI detection. "
|
||||
"It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
|
||||
"If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
|
||||
"\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* High power consumption on mobile devices\n"
|
||||
"* Flexible settings\n"
|
||||
"* Not recognised by detection systems\n"
|
||||
"* Works over TCP network protocol, 443 port.\n") },
|
||||
"* Flexible configuration options\n"
|
||||
"* Undetectable by DPI systems\n"
|
||||
"* Operates over TCP protocol on port 443") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
|
||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
||||
"While WireGuard is known for its efficiency, "
|
||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
||||
"making its traffic blend in with regular internet traffic.\n"
|
||||
"This means that AmneziaWG keeps the fast performance of the original "
|
||||
"while adding an extra layer of stealth, "
|
||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Not recognised by traffic analysis systems\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
|
||||
"It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
|
||||
"However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Low power consumption on mobile devices\n"
|
||||
"* Minimal configuration required\n"
|
||||
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Awg2,
|
||||
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
||||
"combining simplified architecture with high performance across all devices. "
|
||||
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
||||
"making VPN traffic indistinguishable from regular internet traffic.\n"
|
||||
"\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Low battery consumption on mobile devices\n"
|
||||
"* Minimal settings required\n"
|
||||
"* Undetectable by traffic analysis systems (DPI)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
|
||||
"It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, "
|
||||
"thus presenting an authentic TLS certificate and data. \n"
|
||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||
"legitimate sites without the need for specific configurations. \n"
|
||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
|
||||
"This makes REALITY a robust solution for maintaining internet freedom.")
|
||||
},
|
||||
QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
|
||||
"REALITY identifies censorship systems during the TLS handshake, "
|
||||
"redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
|
||||
"This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
|
||||
"\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
|
||||
"effectively protecting against DPI and other traffic analysis methods.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Resistant to active probing and DPI detection\n"
|
||||
"* No special configuration required to disguise traffic\n"
|
||||
"* Highly effective in heavily censored regions\n"
|
||||
"* Minimal battery consumption on devices\n"
|
||||
"* Operates over TCP protocol") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||
"making it particularly adaptive in dynamic network environments. \n"
|
||||
"While it offers a blend of security, stability, and speed, "
|
||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
||||
"* Available in the AmneziaVPN only on Windows\n"
|
||||
"* Low power consumption, on mobile devices\n"
|
||||
"* Minimal configuration\n"
|
||||
"* Recognised by DPI analysis systems\n"
|
||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
||||
QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. "
|
||||
"It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
|
||||
"While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available in AmneziaVPN only on Windows\n"
|
||||
"* Low battery consumption on mobile devices\n"
|
||||
"* Minimal configuration required\n"
|
||||
"* Detectable by DPI analysis systems(easily blocked)\n"
|
||||
"* Operates over UDP protocol(ports 500 and 4500)") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||
@@ -257,6 +253,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
case DockerContainer::Cloak: return Proto::Cloak;
|
||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
case DockerContainer::Xray: return Proto::Xray;
|
||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||
@@ -270,21 +267,49 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
}
|
||||
}
|
||||
|
||||
QString ContainerProps::containerTypeToProtocolString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
|
||||
Proto p = defaultProtocol(c);
|
||||
return ProtocolProps::protoToString(p);
|
||||
}
|
||||
|
||||
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
return true;
|
||||
|
||||
#elif defined(Q_OS_IOS)
|
||||
// Standard iOS build (without Network Extension limitations)
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
// case DockerContainer::ShadowSocks: return true;
|
||||
default: return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(MACOS_NE)
|
||||
// macOS build using Network Extension – hide OpenVPN-based containers
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
case DockerContainer::SSXray: return true;
|
||||
case DockerContainer::OpenVpn:
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
switch (c) {
|
||||
@@ -298,6 +323,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::Xray: return true;
|
||||
@@ -327,7 +353,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::Awg: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@@ -335,7 +361,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::Awg: return tr("Automatic");
|
||||
case DockerContainer::Awg2: return tr("Automatic");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -343,7 +369,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
|
||||
case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
|
||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
||||
default: return "";
|
||||
}
|
||||
@@ -352,7 +378,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::Awg: return 1;
|
||||
case DockerContainer::Awg2: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
@@ -368,6 +394,12 @@ bool ContainerProps::isShareable(DockerContainer container)
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isAwgContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
|
||||
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||
@@ -385,7 +417,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
|
||||
case DockerContainer::Cloak: return 5;
|
||||
case DockerContainer::ShadowSocks: return 6;
|
||||
case DockerContainer::WireGuard: return 2;
|
||||
case DockerContainer::Awg: return 1;
|
||||
case DockerContainer::Awg2: return 1;
|
||||
case DockerContainer::Xray: return 3;
|
||||
case DockerContainer::Ipsec: return 7;
|
||||
case DockerContainer::SSXray: return 8;
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace amnezia
|
||||
enum DockerContainer {
|
||||
None = 0,
|
||||
Awg,
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
@@ -45,6 +46,7 @@ namespace amnezia
|
||||
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
||||
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
||||
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
||||
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
|
||||
|
||||
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
||||
|
||||
@@ -71,6 +73,9 @@ namespace amnezia
|
||||
|
||||
static bool isShareable(amnezia::DockerContainer container);
|
||||
|
||||
static bool isAwgContainer(amnezia::DockerContainer container);
|
||||
|
||||
|
||||
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
static int installPageOrder(amnezia::DockerContainer container);
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace apiDefs
|
||||
AmneziaFreeV3,
|
||||
AmneziaPremiumV1,
|
||||
AmneziaPremiumV2,
|
||||
SelfHosted
|
||||
SelfHosted,
|
||||
ExternalPremium
|
||||
};
|
||||
|
||||
enum ConfigSource {
|
||||
@@ -21,12 +22,21 @@ namespace apiDefs
|
||||
namespace key
|
||||
{
|
||||
constexpr QLatin1String configVersion("config_version");
|
||||
constexpr QLatin1String apiEndpoint("api_endpoint");
|
||||
constexpr QLatin1String apiKey("api_key");
|
||||
constexpr QLatin1String description("description");
|
||||
constexpr QLatin1String name("name");
|
||||
constexpr QLatin1String protocol("protocol");
|
||||
|
||||
constexpr QLatin1String apiConfig("api_config");
|
||||
constexpr QLatin1String stackType("stack_type");
|
||||
constexpr QLatin1String serviceType("service_type");
|
||||
constexpr QLatin1String cliVersion("cli_version");
|
||||
constexpr QLatin1String supportedProtocols("supported_protocols");
|
||||
|
||||
constexpr QLatin1String vpnKey("vpn_key");
|
||||
constexpr QLatin1String config("config");
|
||||
constexpr QLatin1String configs("configs");
|
||||
|
||||
constexpr QLatin1String installationUuid("installation_uuid");
|
||||
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
||||
@@ -37,12 +47,36 @@ namespace apiDefs
|
||||
constexpr QLatin1String serverCountryName("server_country_name");
|
||||
|
||||
constexpr QLatin1String osVersion("os_version");
|
||||
constexpr QLatin1String appLanguage("app_language");
|
||||
|
||||
constexpr QLatin1String availableCountries("available_countries");
|
||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||
constexpr QLatin1String subscriptionDescription("subscription_description");
|
||||
|
||||
constexpr QLatin1String supportInfo("support_info");
|
||||
constexpr QLatin1String email("email");
|
||||
constexpr QLatin1String billingEmail("billing_email");
|
||||
constexpr QLatin1String website("website");
|
||||
constexpr QLatin1String websiteName("website_name");
|
||||
constexpr QLatin1String telegram("telegram");
|
||||
|
||||
constexpr QLatin1String id("id");
|
||||
constexpr QLatin1String orderId("order_id");
|
||||
constexpr QLatin1String migrationCode("migration_code");
|
||||
|
||||
constexpr QLatin1String transactionId("transaction_id");
|
||||
constexpr QLatin1String isTestPurchase("is_test_purchase");
|
||||
|
||||
constexpr QLatin1String userCountryCode("user_country_code");
|
||||
|
||||
constexpr QLatin1String serviceInfo("service_info");
|
||||
constexpr QLatin1String isAdVisible("is_ad_visible");
|
||||
constexpr QLatin1String adHeader("ad_header");
|
||||
constexpr QLatin1String adDescription("ad_description");
|
||||
constexpr QLatin1String adEndpoint("ad_endpoint");
|
||||
}
|
||||
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
#include "apiUtils.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace
|
||||
{
|
||||
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
||||
|
||||
QString escapeUnicode(const QString &input)
|
||||
{
|
||||
QString output;
|
||||
for (QChar c : input) {
|
||||
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
|
||||
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
|
||||
} else {
|
||||
output += c;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||
return endDate < now;
|
||||
}
|
||||
@@ -23,24 +42,34 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||
|
||||
switch (configVersion) {
|
||||
case apiDefs::ConfigSource::Telegram: {
|
||||
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||
|
||||
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||
|
||||
if (apiEndpoint.contains(premiumV1Endpoint)) {
|
||||
return apiDefs::ConfigType::AmneziaPremiumV1;
|
||||
} else if (apiEndpoint.contains(freeV2Endpoint)) {
|
||||
return apiDefs::ConfigType::AmneziaFreeV2;
|
||||
}
|
||||
};
|
||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||
constexpr QLatin1String stackPremium("prem");
|
||||
constexpr QLatin1String stackFree("free");
|
||||
|
||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||
constexpr QLatin1String serviceFree("amnezia-free");
|
||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||
|
||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||
auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString();
|
||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||
|
||||
if (serviceType == servicePremium || stackType == stackPremium) {
|
||||
if (serviceType == servicePremium) {
|
||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||
} else if (serviceType == serviceFree || stackType == stackFree) {
|
||||
} else if (serviceType == serviceFree) {
|
||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||
} else if (serviceType == serviceExternalPremium) {
|
||||
return apiDefs::ConfigType::ExternalPremium;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
@@ -54,30 +83,45 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
|
||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||
}
|
||||
|
||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||
const QByteArray &responseBody)
|
||||
{
|
||||
const int httpStatusCodeConflict = 409;
|
||||
const int httpStatusCodeNotFound = 404;
|
||||
const int httpStatusCodeNotImplemented = 501;
|
||||
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return amnezia::ErrorCode::ApiConfigSslError;
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
} else if (replyError == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << replyError;
|
||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
qDebug() << replyError;
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << QString::fromUtf8(responseBody);
|
||||
qDebug() << replyError;
|
||||
qDebug() << replyErrorString;
|
||||
qDebug() << httpStatusCode;
|
||||
if (httpStatusCode == httpStatusCodeConflict) {
|
||||
|
||||
int httpStatusFromBody = -1;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
|
||||
}
|
||||
|
||||
if (httpStatusFromBody == httpStatusCodeConflict) {
|
||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||
} else if (httpStatusCode == httpStatusCodeNotFound) {
|
||||
} else if (httpStatusFromBody == httpStatusCodeNotFound) {
|
||||
return amnezia::ErrorCode::ApiNotFoundError;
|
||||
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
}
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
@@ -85,3 +129,96 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
qDebug() << "something went wrong";
|
||||
return amnezia::ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||
apiDefs::ConfigType::ExternalPremium };
|
||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||
}
|
||||
|
||||
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QPair<QString, QVariant>> orderedFields;
|
||||
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
|
||||
|
||||
QString vpnKeyStr = "{";
|
||||
for (int i = 0; i < orderedFields.size(); ++i) {
|
||||
const auto &pair = orderedFields[i];
|
||||
if (pair.second.typeId() == QMetaType::Type::QString) {
|
||||
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
|
||||
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
|
||||
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
|
||||
}
|
||||
|
||||
if (i < orderedFields.size() - 1) {
|
||||
vpnKeyStr += ", ";
|
||||
}
|
||||
}
|
||||
vpnKeyStr += "}";
|
||||
|
||||
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||
|
||||
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||
|
||||
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||
}
|
||||
|
||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString vpnKeyText = "";
|
||||
|
||||
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
|
||||
|
||||
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
|
||||
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
|
||||
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
|
||||
|
||||
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
|
||||
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
|
||||
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
|
||||
|
||||
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
|
||||
|
||||
QString vpnKeyStr = "{";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
|
||||
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
|
||||
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
|
||||
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
|
||||
vpnKeyStr += "}, ";
|
||||
|
||||
vpnKeyStr += "\"auth_data\": {";
|
||||
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
|
||||
vpnKeyStr += "}";
|
||||
|
||||
vpnKeyStr += "}";
|
||||
|
||||
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||
|
||||
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||
|
||||
return vpnKeyText;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,17 @@ namespace apiUtils
|
||||
|
||||
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||
|
||||
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
||||
|
||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||
|
||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||
const QByteArray &responseBody);
|
||||
|
||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
||||
}
|
||||
|
||||
#endif // APIUTILS_H
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "coreController.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QTranslator>
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
@@ -25,9 +26,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
|
||||
initNotificationHandler();
|
||||
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
updateTranslator(m_settings->getAppLanguage());
|
||||
}
|
||||
|
||||
void CoreController::initModels()
|
||||
@@ -47,6 +47,9 @@ void CoreController::initModels()
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
|
||||
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
|
||||
|
||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||
|
||||
@@ -96,6 +99,9 @@ void CoreController::initModels()
|
||||
|
||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||
|
||||
m_newsModel.reset(new NewsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
|
||||
}
|
||||
|
||||
void CoreController::initControllers()
|
||||
@@ -116,6 +122,9 @@ void CoreController::initControllers()
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||
|
||||
connect(m_installController.get(), &InstallController::profileCleared,
|
||||
m_protocolsModel.get(), &ProtocolsModel::updateModel);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
@@ -129,6 +138,9 @@ void CoreController::initControllers()
|
||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
|
||||
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
|
||||
|
||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||
|
||||
@@ -141,6 +153,12 @@ void CoreController::initControllers()
|
||||
|
||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||
|
||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||
|
||||
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
@@ -213,11 +231,14 @@ void CoreController::initSignalHandlers()
|
||||
initAutoConnectHandler();
|
||||
initAmneziaDnsToggledHandler();
|
||||
initPrepareConfigHandler();
|
||||
initImportPremiumV2VpnKeyHandler();
|
||||
initShowMigrationDrawerHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
}
|
||||
|
||||
void CoreController::initNotificationHandler()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
@@ -229,7 +250,10 @@ void CoreController::initNotificationHandler()
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
|
||||
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
|
||||
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
@@ -238,7 +262,23 @@ void CoreController::updateTranslator(const QLocale &locale)
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
}
|
||||
|
||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
||||
QStringList availableTranslations;
|
||||
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
|
||||
while (it.hasNext()) {
|
||||
availableTranslations << it.next();
|
||||
}
|
||||
|
||||
// This code allow to load translation for the language only, without country code
|
||||
const QString lang = locale.name().split("_").first();
|
||||
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
|
||||
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||
for (const QString &translation : availableTranslations) {
|
||||
if (translation.contains(translationFilePrefix)) {
|
||||
strFileName = translation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_translator->load(strFileName)) {
|
||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
@@ -250,6 +290,7 @@ void CoreController::updateTranslator(const QLocale &locale)
|
||||
m_engine->retranslate();
|
||||
|
||||
emit translationsUpdated();
|
||||
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
|
||||
}
|
||||
|
||||
void CoreController::initErrorMessagesHandler()
|
||||
@@ -270,13 +311,10 @@ void CoreController::setQmlRoot()
|
||||
|
||||
void CoreController::initApiCountryModelUpdateHandler()
|
||||
{
|
||||
// TODO
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||
});
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||
}
|
||||
|
||||
void CoreController::initContainerModelUpdateHandler()
|
||||
@@ -284,6 +322,11 @@ void CoreController::initContainerModelUpdateHandler()
|
||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||
&ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
|
||||
if (m_serversModel->hasServersFromGatewayApi()) {
|
||||
m_apiNewsController->fetchNews(false);
|
||||
}
|
||||
});
|
||||
m_serversModel->resetModel();
|
||||
}
|
||||
|
||||
@@ -339,7 +382,51 @@ void CoreController::initPrepareConfigHandler()
|
||||
});
|
||||
}
|
||||
|
||||
void CoreController::initImportPremiumV2VpnKeyHandler()
|
||||
{
|
||||
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
|
||||
m_importController->extractConfigFromData(vpnKey);
|
||||
m_importController->importConfig();
|
||||
|
||||
emit m_apiPremV1MigrationController->migrationFinished();
|
||||
});
|
||||
}
|
||||
|
||||
void CoreController::initShowMigrationDrawerHandler()
|
||||
{
|
||||
QTimer::singleShot(1000, this, [this]() {
|
||||
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
|
||||
m_apiPremV1MigrationController->showMigrationDrawer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreController::initStrictKillSwitchHandler()
|
||||
{
|
||||
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
||||
&VpnConnection::onKillSwitchModeChanged);
|
||||
}
|
||||
|
||||
QSharedPointer<PageController> CoreController::pageController() const
|
||||
{
|
||||
return m_pageController;
|
||||
}
|
||||
|
||||
void CoreController::openConnectionByIndex(int serverIndex)
|
||||
{
|
||||
if (m_serversModel) {
|
||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
||||
m_serversModel->setDefaultServerIndex(serverIndex);
|
||||
}
|
||||
m_connectionController->toggleConnection();
|
||||
}
|
||||
|
||||
void CoreController::importConfigFromData(const QString &data)
|
||||
{
|
||||
if (!m_importController)
|
||||
return;
|
||||
|
||||
if (m_importController->extractConfigFromData(data)) {
|
||||
m_importController->importConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,16 @@
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/systemtray_notificationhandler.h"
|
||||
#endif
|
||||
|
||||
#include "ui/controllers/api/apiConfigsController.h"
|
||||
#include "ui/controllers/api/apiSettingsController.h"
|
||||
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||
#include "ui/controllers/api/apiNewsController.h"
|
||||
#include "ui/controllers/appSplitTunnelingController.h"
|
||||
#include "ui/controllers/allowedDnsController.h"
|
||||
#include "ui/controllers/connectionController.h"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/focusController.h"
|
||||
@@ -18,6 +25,7 @@
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
|
||||
#include "ui/models/allowed_dns_model.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
@@ -40,8 +48,9 @@
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
|
||||
@@ -56,8 +65,12 @@ public:
|
||||
QSharedPointer<PageController> pageController() const;
|
||||
void setQmlRoot();
|
||||
|
||||
void openConnectionByIndex(int serverIndex);
|
||||
void importConfigFromData(const QString &data);
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
|
||||
private:
|
||||
void initModels();
|
||||
@@ -80,13 +93,16 @@ private:
|
||||
void initAutoConnectHandler();
|
||||
void initAmneziaDnsToggledHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initImportPremiumV2VpnKeyHandler();
|
||||
void initShowMigrationDrawerHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
|
||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
@@ -102,9 +118,12 @@ private:
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||
QScopedPointer<AllowedDnsController> m_allowedDnsController;
|
||||
|
||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||
QScopedPointer<ApiNewsController> m_apiNewsController;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
@@ -112,6 +131,8 @@ private:
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<NewsModel> m_newsModel;
|
||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
#include "gatewayController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "core/api/apiUtils.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/ipcclient.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
@@ -30,86 +39,64 @@ namespace
|
||||
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||
|
||||
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
|
||||
|
||||
constexpr int httpStatusCodeNotFound = 404;
|
||||
constexpr int httpStatusCodeConflict = 409;
|
||||
|
||||
constexpr int httpStatusCodeNotImplemented = 501;
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
||||
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs)
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_gatewayEndpoint(gatewayEndpoint),
|
||||
m_isDevEnvironment(isDevEnvironment),
|
||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
||||
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||
{
|
||||
EncryptedRequestData encRequestData;
|
||||
encRequestData.errorCode = ErrorCode::NoError;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||
|
||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||
|
||||
QNetworkReply *reply;
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||
auto requestFunction = [&request, &responseBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->get(request);
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
||||
const QList<QSslError> &nestedSslErrors) {
|
||||
responseBody = nestedReply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
||||
sslErrors = nestedSslErrors;
|
||||
reply = nestedReply;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(encRequestData.request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue())
|
||||
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
@@ -124,71 +111,217 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
return ErrorCode::ApiMissingAgwPublicKey;
|
||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
|
||||
"", encRequestData.salt);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
|
||||
QNetworkReply::NetworkError replyError, const QByteArray &key,
|
||||
const QByteArray &iv, const QByteArray &salt)
|
||||
{
|
||||
DecryptionResult result;
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
result.isDecryptionSuccessful = true;
|
||||
} catch (...) {
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
return encRequestData.errorCode;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
reply->deleteLater();
|
||||
|
||||
auto decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||
encRequestData.request.setUrl(url);
|
||||
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = nestedReply->readAll();
|
||||
reply = nestedReply;
|
||||
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
|
||||
&decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = reply->readAll();
|
||||
replyErrorString = reply->errorString();
|
||||
replyError = reply->error();
|
||||
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
if (!sslErrors.isEmpty()
|
||||
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||
sslErrors = nestedSslErrors;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
auto errorCode =
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
try {
|
||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
return ErrorCode::NoError;
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
if (!decryptionResult.isDecryptionSuccessful) {
|
||||
qCritical() << "error when decrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
responseBody = decryptionResult.decryptedBody;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls()
|
||||
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||
{
|
||||
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
||||
promise->start();
|
||||
|
||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
|
||||
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
|
||||
const QString &replyErrorString, int httpStatusCode) {
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
|
||||
decryptionResult.decryptedBody);
|
||||
if (errorCode) {
|
||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decryptionResult.isDecryptionSuccessful) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||
promise->finish();
|
||||
return;
|
||||
}
|
||||
|
||||
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
|
||||
promise->finish();
|
||||
};
|
||||
|
||||
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||
|
||||
QStringList baseUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
} else {
|
||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
}
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
|
||||
+ ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &baseUrl : baseUrls)
|
||||
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
||||
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
|
||||
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
|
||||
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
|
||||
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
|
||||
const QString &replyErrorString, int httpStatusCode) {
|
||||
GatewayController::DecryptionResult result;
|
||||
result.decryptedBody = decryptedBody;
|
||||
result.isDecryptionSuccessful = isDecryptionSuccessful;
|
||||
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||
}
|
||||
});
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
@@ -198,22 +331,33 @@ QStringList GatewayController::getProxyUrls()
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
QStringList baseUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
} else {
|
||||
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
}
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
@@ -251,66 +395,271 @@ QStringList GatewayController::getProxyUrls()
|
||||
}
|
||||
return endpoints;
|
||||
} else {
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << replyError;
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||
const QByteArray &iv, const QByteArray &salt)
|
||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||
bool isDecryptionSuccessful)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "Timeout occurred";
|
||||
const QByteArray &responseBody = decryptedResponseBody;
|
||||
|
||||
int httpStatus = -1;
|
||||
if (isDecryptionSuccessful) {
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
httpStatus = jsonObj.value("http_status").toInt(-1);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "timeout occurred";
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
qDebug() << "The response contains an html tag";
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||
} else if (httpStatus == httpStatusCodeNotFound) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
} else if (httpStatus == httpStatusCodeNotImplemented) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
} else if (httpStatus == httpStatusCodeConflict) {
|
||||
return false;
|
||||
} else if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||
} catch (...) {
|
||||
qDebug() << "Failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
||||
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||
{
|
||||
QStringList proxyUrls = getProxyUrls();
|
||||
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QByteArray responseBody;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (replyProcessingFunction(reply, sslErrors)) {
|
||||
auto result = replyProcessingFunction(reply, sslErrors);
|
||||
reply->deleteLater();
|
||||
return result;
|
||||
};
|
||||
|
||||
if (m_proxyUrl.isEmpty()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
request.setUrl(proxyUrl + "lmbd-health");
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
reply->deleteLater();
|
||||
|
||||
m_proxyUrl = proxyUrl;
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_proxyUrl.isEmpty()) {
|
||||
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||
m_proxyUrl = proxyUrl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete)
|
||||
{
|
||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||
onComplete({});
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray encrypted = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray h = hash.result().toHex();
|
||||
|
||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
||||
|
||||
QSimpleCrypto::QBlockCipher cipher;
|
||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||
} else {
|
||||
responseBody = encrypted;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error decrypting payload";
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
QStringList endpoints;
|
||||
for (const QJsonValue &endpoint : endpointsArray)
|
||||
endpoints.push_back(endpoint.toString());
|
||||
|
||||
QStringList shuffled = endpoints;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
|
||||
onComplete(shuffled);
|
||||
return;
|
||||
}
|
||||
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
reply->deleteLater();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
|
||||
std::function<void(const QString &)> onComplete)
|
||||
{
|
||||
if (currentProxyIndex >= proxyUrls.size()) {
|
||||
onComplete("");
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(1000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
|
||||
// *(state->sslErrors) = e;
|
||||
// });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
m_proxyUrl = proxyUrls[currentProxyIndex];
|
||||
onComplete(m_proxyUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
|
||||
{
|
||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||
if (proxyUrl.isEmpty()) {
|
||||
onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request = encRequestData.request;
|
||||
request.setUrl(endpoint.arg(proxyUrl));
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
|
||||
|
||||
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
auto decryptionResult =
|
||||
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||
|
||||
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
|
||||
httpStatusCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#ifndef GATEWAYCONTROLLER_H
|
||||
#define GATEWAYCONTROLLER_H
|
||||
|
||||
#include <QFuture>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPromise>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "core/defs.h"
|
||||
|
||||
@@ -15,21 +19,52 @@ class GatewayController : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr);
|
||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||
|
||||
private:
|
||||
QStringList getProxyUrls();
|
||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
struct EncryptedRequestData
|
||||
{
|
||||
QNetworkRequest request;
|
||||
QByteArray requestBody;
|
||||
QByteArray key;
|
||||
QByteArray iv;
|
||||
QByteArray salt;
|
||||
amnezia::ErrorCode errorCode;
|
||||
};
|
||||
|
||||
struct DecryptionResult
|
||||
{
|
||||
QByteArray decryptedBody;
|
||||
bool isDecryptionSuccessful;
|
||||
};
|
||||
|
||||
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
|
||||
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
|
||||
|
||||
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
|
||||
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
std::function<void(const QStringList &)> onComplete);
|
||||
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
||||
void bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
|
||||
|
||||
int m_requestTimeoutMsecs;
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
#endif // GATEWAYCONTROLLER_H
|
||||
|
||||
@@ -138,7 +138,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
|
||||
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
@@ -146,7 +146,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
return e;
|
||||
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
||||
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
@@ -154,7 +154,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
return e;
|
||||
|
||||
e = runScript(credentials,
|
||||
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
||||
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
|
||||
genVarsForScript(credentials, container)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
@@ -177,7 +177,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
||||
|
||||
errorCode = ErrorCode::NoError;
|
||||
|
||||
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
|
||||
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
@@ -345,11 +345,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
if (ContainerProps::isAwgContainer(container)) {
|
||||
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
||||
@@ -366,8 +366,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|
||||
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
|
||||
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|
||||
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|
||||
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
|
||||
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
|
||||
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|
||||
|| (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
|
||||
!= newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
|
||||
|| (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
|
||||
!= newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -375,7 +380,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -383,6 +388,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Xray) {
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -439,15 +451,24 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode =
|
||||
ErrorCode error =
|
||||
runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut);
|
||||
if (errorCode)
|
||||
return errorCode;
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
return errorCode;
|
||||
if (stdOut.contains("doesn't work on cgroups v2"))
|
||||
return ErrorCode::ServerDockerOnCgroupsV2;
|
||||
if (stdOut.contains("cgroup mountpoint does not exist"))
|
||||
return ErrorCode::ServerCgroupMountpoint;
|
||||
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
|
||||
return ErrorCode::DockerPullRateLimit;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||
@@ -625,6 +646,14 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||
|
||||
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
|
||||
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
|
||||
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
|
||||
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
|
||||
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
|
||||
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
|
||||
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
|
||||
|
||||
// Socks5 proxy vars
|
||||
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||
auto username = socks5ProxyConfig.value(config_key::userName).toString();
|
||||
@@ -633,7 +662,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||
|
||||
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
||||
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
|
||||
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
||||
? NetworkUtilities::getIPAddress(credentials.hostName)
|
||||
: credentials.hostName;
|
||||
if (!serverIp.isEmpty()) {
|
||||
@@ -757,10 +787,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
||||
|
||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
if (credentials.userName == "root") {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
@@ -774,8 +800,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||
|
||||
if (!stdOut.contains("sudo"))
|
||||
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
|
||||
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
|
||||
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
|
||||
return ErrorCode::ServerUserNotInSudo;
|
||||
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||
if (stdOut.contains("password is required"))
|
||||
return ErrorCode::ServerUserPasswordRequired;
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -807,7 +841,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
||||
|
||||
if (stdOut.contains("Packet manager not found"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("fuser not installed"))
|
||||
if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed"))
|
||||
return ErrorCode::NoError;
|
||||
|
||||
if (stdOut.isEmpty()) {
|
||||
|
||||
@@ -99,11 +99,12 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
|
||||
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
||||
|
||||
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
|
||||
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
|
||||
// add mtu for old configs
|
||||
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
||||
vpnConfigData[config_key::mtu] =
|
||||
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
|
||||
protocols::wireguard::defaultMtu;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,13 @@ namespace amnezia
|
||||
ServerCancelInstallation = 204,
|
||||
ServerUserNotInSudo = 205,
|
||||
ServerPacketManagerError = 206,
|
||||
ServerSudoPackageIsNotPreinstalled = 207,
|
||||
ServerUserDirectoryNotAccessible = 208,
|
||||
ServerUserNotAllowedInSudoers = 209,
|
||||
ServerUserPasswordRequired = 210,
|
||||
ServerDockerOnCgroupsV2 = 211,
|
||||
ServerCgroupMountpoint = 212,
|
||||
DockerPullRateLimit = 213,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
@@ -111,6 +118,10 @@ namespace amnezia
|
||||
ApiServicesMissingError = 1107,
|
||||
ApiConfigLimitError = 1108,
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
ApiSubscriptionExpiredError = 1112,
|
||||
ApiPurchaseError = 1113,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -20,8 +20,15 @@ QString errorString(ErrorCode code) {
|
||||
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break;
|
||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Package manager error"); break;
|
||||
case(ErrorCode::ServerSudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed on the server"); break;
|
||||
case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break;
|
||||
case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break;
|
||||
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
|
||||
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
|
||||
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
||||
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
@@ -68,6 +75,10 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
|
||||
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
|
||||
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
||||
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
||||
@@ -1,108 +1,71 @@
|
||||
#include "ipcclient.h"
|
||||
#include "ipc.h"
|
||||
#include <QRemoteObjectNode>
|
||||
|
||||
IpcClient *IpcClient::m_instance = nullptr;
|
||||
#include <QtNetwork/qlocalsocket.h>
|
||||
|
||||
IpcClient::IpcClient(QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
|
||||
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
|
||||
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
|
||||
}
|
||||
|
||||
IpcClient::~IpcClient()
|
||||
IpcClient& IpcClient::Instance()
|
||||
{
|
||||
if (m_localSocket)
|
||||
m_localSocket->close();
|
||||
}
|
||||
|
||||
bool IpcClient::isSocketConnected() const
|
||||
{
|
||||
return m_isSocketConnected;
|
||||
}
|
||||
|
||||
IpcClient *IpcClient::Instance()
|
||||
{
|
||||
return m_instance;
|
||||
thread_local IpcClient ipcClient;
|
||||
return ipcClient;
|
||||
}
|
||||
|
||||
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
||||
{
|
||||
if (!Instance())
|
||||
QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
|
||||
if (rep.isNull()) {
|
||||
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
|
||||
return nullptr;
|
||||
return Instance()->m_ipcClient;
|
||||
}
|
||||
if (!rep->waitForSource(1000)) {
|
||||
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
|
||||
return nullptr;
|
||||
}
|
||||
if (!rep->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::Interface(): Replica is invalid";
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
||||
{
|
||||
if (!Instance())
|
||||
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
|
||||
if (rep.isNull()) {
|
||||
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
|
||||
return nullptr;
|
||||
return Instance()->m_Tun2SocksClient;
|
||||
}
|
||||
|
||||
bool IpcClient::init(IpcClient *instance)
|
||||
{
|
||||
m_instance = instance;
|
||||
|
||||
Instance()->m_localSocket = new QLocalSocket(Instance());
|
||||
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
|
||||
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
|
||||
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
|
||||
cliNode->waitForSource(5000);
|
||||
Instance()->m_ipcClient.reset(cliNode);
|
||||
|
||||
if (!Instance()->m_ipcClient) {
|
||||
qWarning() << "IpcClient is not ready!";
|
||||
}
|
||||
|
||||
Instance()->m_ipcClient->waitForSource(1000);
|
||||
|
||||
if (!Instance()->m_ipcClient->isReplicaValid()) {
|
||||
qWarning() << "IpcClient replica is not connected!";
|
||||
}
|
||||
|
||||
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
|
||||
t2sNode->waitForSource(5000);
|
||||
Instance()->m_Tun2SocksClient.reset(t2sNode);
|
||||
|
||||
if (!Instance()->m_Tun2SocksClient) {
|
||||
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
|
||||
}
|
||||
|
||||
Instance()->m_Tun2SocksClient->waitForSource(1000);
|
||||
|
||||
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
|
||||
}
|
||||
});
|
||||
|
||||
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
|
||||
[instance]() { instance->m_isSocketConnected = false; });
|
||||
|
||||
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
|
||||
Instance()->m_localSocket->waitForConnected();
|
||||
|
||||
if (!Instance()->m_ipcClient) {
|
||||
qDebug() << "IpcClient::init failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "IpcClient::init succeed";
|
||||
|
||||
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||
if (!rep->waitForSource(1000)) {
|
||||
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
|
||||
return nullptr;
|
||||
}
|
||||
if (!rep->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
{
|
||||
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
|
||||
QSharedPointer<IpcInterfaceReplica> rep = Interface();
|
||||
if (!rep) {
|
||||
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
||||
futureResult.waitForFinished(5000);
|
||||
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
|
||||
if (!pidReply.waitForFinished(5000)){
|
||||
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int pid = futureResult.returnValue();
|
||||
|
||||
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
|
||||
Instance()->m_processNodes.insert(pid, pd);
|
||||
int pid = pidReply.returnValue();
|
||||
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
|
||||
|
||||
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
|
||||
|
||||
@@ -110,6 +73,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
|
||||
|
||||
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
|
||||
// TODO: rework the unsafe cast below
|
||||
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
|
||||
pd->ipcProcess.reset(priv);
|
||||
if (!pd->ipcProcess) {
|
||||
@@ -124,8 +88,12 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
[pd]() { pd->replicaNode->deleteLater(); });
|
||||
}
|
||||
});
|
||||
|
||||
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
||||
pd->localSocket->waitForConnected();
|
||||
if (!pd->localSocket->waitForConnected()) {
|
||||
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||
return processReplica;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <QLocalSocket>
|
||||
#include <QObject>
|
||||
|
||||
#include "ipc.h"
|
||||
#include "rep_ipc_interface_replica.h"
|
||||
#include "rep_ipc_process_tun2socks_replica.h"
|
||||
|
||||
@@ -14,27 +13,48 @@ class IpcClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IpcClient(QObject *parent = nullptr);
|
||||
explicit IpcClient(QObject *parent = nullptr);
|
||||
|
||||
static IpcClient *Instance();
|
||||
static bool init(IpcClient *instance);
|
||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||
static IpcClient& Instance();
|
||||
|
||||
bool isSocketConnected() const;
|
||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||
|
||||
template <typename Func>
|
||||
static auto withInterface(Func func)
|
||||
{
|
||||
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
|
||||
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
|
||||
|
||||
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::withInterface(): Service is not running";
|
||||
|
||||
if constexpr (std::is_void_v<ReturnType>)
|
||||
return;
|
||||
else
|
||||
return ReturnType{};
|
||||
}
|
||||
|
||||
return func(iface);
|
||||
}
|
||||
|
||||
template <typename OnSuccess, typename OnFailure>
|
||||
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
|
||||
{
|
||||
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
|
||||
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
|
||||
return onFailure();
|
||||
}
|
||||
|
||||
return onSuccess(iface);
|
||||
}
|
||||
signals:
|
||||
|
||||
private:
|
||||
~IpcClient() override;
|
||||
|
||||
QRemoteObjectNode m_ClientNode;
|
||||
QRemoteObjectNode m_Tun2SocksNode;
|
||||
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
|
||||
QPointer<QLocalSocket> m_localSocket;
|
||||
QPointer<QLocalSocket> m_tun2socksSocket;
|
||||
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
|
||||
QRemoteObjectNode m_node;
|
||||
QSharedPointer<IpcInterfaceReplica> m_interface;
|
||||
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
|
||||
|
||||
struct ProcessDescriptor {
|
||||
ProcessDescriptor () {
|
||||
@@ -46,11 +66,6 @@ private:
|
||||
QSharedPointer<QRemoteObjectNode> replicaNode;
|
||||
QSharedPointer<QLocalSocket> localSocket;
|
||||
};
|
||||
|
||||
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
|
||||
bool m_isSocketConnected {false};
|
||||
|
||||
static IpcClient *m_instance;
|
||||
};
|
||||
|
||||
#endif // IPCCLIENT_H
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#include "networkUtilities.h"
|
||||
#include <QtNetwork/qnetworkinterface.h>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <Ipexport.h>
|
||||
#include <Ws2tcpip.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <stdint.h>
|
||||
#include <Iphlpapi.h>
|
||||
#include <Iptypes.h>
|
||||
#include <WinSock2.h>
|
||||
#include <winsock.h>
|
||||
#include <QNetworkInterface>
|
||||
#include "qendian.h"
|
||||
#include <QSettings>
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <arpa/inet.h>
|
||||
@@ -22,13 +24,22 @@
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
#include <QHostAddress>
|
||||
@@ -169,7 +180,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||
#ifdef Q_OS_WIN
|
||||
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||
<< dst.toString();
|
||||
quint32_be ipBigEndian;
|
||||
quint32 ipBigEndian;
|
||||
quint32 ip = dst.toIPv4Address();
|
||||
qToBigEndian(ip, &ipBigEndian);
|
||||
_MIB_IPFORWARDROW routeInfo;
|
||||
@@ -185,6 +196,17 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool NetworkUtilities::checkIpv6Enabled() {
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",
|
||||
QSettings::NativeFormat);
|
||||
int ret = RegHLM.value("DisabledComponents", 0).toInt();
|
||||
qDebug() << "Check for Windows disabled IPv6 return " << ret;
|
||||
return (ret != 255);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||
const ULONG Flags,
|
||||
@@ -227,12 +249,14 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||
}
|
||||
#endif
|
||||
|
||||
QString NetworkUtilities::getGatewayAndIface()
|
||||
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
constexpr int BUFF_LEN = 100;
|
||||
char buff[BUFF_LEN] = {'\0'};
|
||||
QString result;
|
||||
|
||||
QString resGateway;
|
||||
int resIndex = -1;
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||
DWORD dwRetVal =
|
||||
@@ -240,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||
@@ -255,7 +279,9 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
struct sockaddr_in addr;
|
||||
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||
qDebug() << "this is true v4 !";
|
||||
result = gw;
|
||||
|
||||
resGateway = gw;
|
||||
resIndex = pCurAddress->IfIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +289,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
}
|
||||
|
||||
free(pAdapterAddresses);
|
||||
return result;
|
||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
constexpr int BUFFER_SIZE = 100;
|
||||
@@ -280,7 +306,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
|
||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||
perror("socket failed");
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
memset(msgbuf, 0, sizeof(msgbuf));
|
||||
@@ -304,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
/* send msg */
|
||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||
perror("send failed");
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
/* receive response */
|
||||
@@ -313,7 +339,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||
if (received_bytes < 0) {
|
||||
perror("Error in recv");
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
@@ -323,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
/* If we received all data break */
|
||||
@@ -376,10 +402,12 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
return gateway_address;
|
||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QString gateway;
|
||||
int index = -1;
|
||||
|
||||
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||
int afinet_type[] = {AF_INET, AF_INET6};
|
||||
|
||||
@@ -389,17 +417,17 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
|
||||
size_t needed = 0;
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||
return "";
|
||||
return {};
|
||||
|
||||
char* buf;
|
||||
if ((buf = new char[needed]) == 0)
|
||||
return "";
|
||||
return {};
|
||||
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||
{
|
||||
qDebug() << "sysctl: net.route.0.0.dump";
|
||||
delete[] buf;
|
||||
return gateway;
|
||||
return {};
|
||||
}
|
||||
|
||||
struct rt_msghdr* rt;
|
||||
@@ -437,7 +465,10 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||
sizeof(struct in_addr));
|
||||
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||
{
|
||||
gateway = dstStr4;
|
||||
index = rt->rtm_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -451,7 +482,10 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||
sizeof(struct in6_addr));
|
||||
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||
{
|
||||
gateway = dstStr6;
|
||||
index = rt->rtm_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -460,6 +494,6 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return gateway;
|
||||
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <QtNetwork/qnetworkinterface.h>
|
||||
|
||||
class NetworkUtilities : public QObject
|
||||
{
|
||||
@@ -16,7 +16,8 @@ public:
|
||||
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||
static bool checkIPv4Format(const QString &ip);
|
||||
static bool checkIpSubnetFormat(const QString &ip);
|
||||
static QString getGatewayAndIface();
|
||||
static bool checkIpv6Enabled();
|
||||
static QPair<QString, QNetworkInterface> getGatewayAndIface();
|
||||
// Returns the Interface Index that could Route to dst
|
||||
static int AdapterIndexTo(const QHostAddress& dst);
|
||||
|
||||
@@ -29,7 +30,6 @@ public:
|
||||
|
||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||
|
||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||
};
|
||||
|
||||
|
||||
194
client/core/osSignalHandler.cpp
Normal file
194
client/core/osSignalHandler.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "osSignalHandler.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMetaObject>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "../amnezia_application.h"
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <unistd.h>
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QAbstractNativeEventFilter>
|
||||
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
class WindowsCloseFilter : public QAbstractNativeEventFilter
|
||||
{
|
||||
public:
|
||||
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
|
||||
{
|
||||
MSG *msg = static_cast<MSG *>(message);
|
||||
|
||||
switch (msg->message) {
|
||||
case WM_CLOSE: {
|
||||
const HWND active = GetActiveWindow();
|
||||
const HWND self = msg->hwnd;
|
||||
if (active != self) {
|
||||
AmneziaApplication *app = qobject_cast<AmneziaApplication *>(QCoreApplication::instance());
|
||||
if (app) {
|
||||
QMetaObject::invokeMethod(app, "forceQuit", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
static WindowsCloseFilter *windowsFilter = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
static int signalFd = -1;
|
||||
static QSocketNotifier *socketNotifier = nullptr;
|
||||
|
||||
static void setupUnixSignalHandler()
|
||||
{
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGINT);
|
||||
sigaddset(&set, SIGTERM);
|
||||
|
||||
pthread_sigmask(SIG_BLOCK, &set, nullptr);
|
||||
|
||||
signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
|
||||
if (signalFd < 0)
|
||||
return;
|
||||
|
||||
socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance());
|
||||
|
||||
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
|
||||
signalfd_siginfo fdsi;
|
||||
::read(signalFd, &fdsi, sizeof(fdsi));
|
||||
|
||||
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
#elif defined(Q_OS_MACOS)
|
||||
static int signalPipe[2] = { -1, -1 };
|
||||
static QSocketNotifier *socketNotifier = nullptr;
|
||||
|
||||
static void macSignalHandler(int)
|
||||
{
|
||||
if (signalPipe[1] >= 0) {
|
||||
const char ch = 1;
|
||||
::write(signalPipe[1], &ch, sizeof(ch));
|
||||
}
|
||||
}
|
||||
|
||||
static void setupUnixSignalHandler()
|
||||
{
|
||||
if (::pipe(signalPipe) != 0)
|
||||
return;
|
||||
|
||||
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
|
||||
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
|
||||
|
||||
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
|
||||
|
||||
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
|
||||
char buf[16];
|
||||
::read(signalPipe[0], buf, sizeof(buf));
|
||||
QCoreApplication::quit();
|
||||
});
|
||||
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = macSignalHandler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void cleanupUnixSignalHandler()
|
||||
{
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
if (socketNotifier) {
|
||||
socketNotifier->setEnabled(false);
|
||||
socketNotifier->deleteLater();
|
||||
socketNotifier = nullptr;
|
||||
}
|
||||
|
||||
if (signalFd >= 0) {
|
||||
::close(signalFd);
|
||||
signalFd = -1;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS)
|
||||
struct sigaction sa {};
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
|
||||
if (socketNotifier) {
|
||||
socketNotifier->setEnabled(false);
|
||||
socketNotifier->deleteLater();
|
||||
socketNotifier = nullptr;
|
||||
}
|
||||
|
||||
if (signalPipe[0] >= 0) {
|
||||
::close(signalPipe[0]);
|
||||
signalPipe[0] = -1;
|
||||
}
|
||||
|
||||
if (signalPipe[1] >= 0) {
|
||||
::close(signalPipe[1]);
|
||||
signalPipe[1] = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (windowsFilter) {
|
||||
QCoreApplication::instance()->removeNativeEventFilter(windowsFilter);
|
||||
delete windowsFilter;
|
||||
windowsFilter = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void OsSignalHandler::setup()
|
||||
{
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
initialized = true;
|
||||
|
||||
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACOS)
|
||||
setupUnixSignalHandler();
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
windowsFilter = new WindowsCloseFilter();
|
||||
QCoreApplication::instance()->installNativeEventFilter(windowsFilter);
|
||||
#endif
|
||||
|
||||
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
|
||||
}
|
||||
17
client/core/osSignalHandler.h
Normal file
17
client/core/osSignalHandler.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef OSSIGNALHANDLER_H
|
||||
#define OSSIGNALHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class OsSignalHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static void setup();
|
||||
|
||||
private:
|
||||
explicit OsSignalHandler(QObject *parent = nullptr);
|
||||
static void handleSignal(int signal);
|
||||
};
|
||||
|
||||
#endif // OSSIGNALHANDLER_H
|
||||
@@ -11,7 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
||||
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
|
||||
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||
case DockerContainer::Awg: return QLatin1String("awg");
|
||||
case DockerContainer::Awg2: return QLatin1String("awg");
|
||||
case DockerContainer::Awg: return QLatin1String("awg_legacy");
|
||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||
case DockerContainer::Xray: return QLatin1String("xray");
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace amnezia::serialization
|
||||
namespace vless
|
||||
{
|
||||
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||
const QString Serialize(const VlessServerObject &server, const QString &alias);
|
||||
} // namespace vless
|
||||
|
||||
namespace ss
|
||||
|
||||
@@ -42,6 +42,25 @@ struct VMessServerObject
|
||||
};
|
||||
|
||||
|
||||
struct VlessServerObject
|
||||
{
|
||||
QString address;
|
||||
QString id; // UUID
|
||||
int port;
|
||||
QString flow = "xtls-rprx-vision";
|
||||
QString encryption = "none";
|
||||
QString network = "tcp";
|
||||
QString security = "reality";
|
||||
QString serverName; // SNI
|
||||
QString publicKey;
|
||||
QString shortId;
|
||||
QString fingerprint = "chrome";
|
||||
QString spiderX = "";
|
||||
JSONSTRUCT_COMPARE(VlessServerObject, address, id, port, flow, encryption)
|
||||
JSONSTRUCT_REGISTER(VlessServerObject, F(address, id, port, flow, encryption, network, security, serverName, publicKey, shortId, fingerprint, spiderX))
|
||||
};
|
||||
|
||||
|
||||
namespace transfer
|
||||
{
|
||||
|
||||
|
||||
@@ -252,5 +252,65 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
||||
root["inbounds"] = QJsonArray { inbound };
|
||||
return root;
|
||||
}
|
||||
} // namespace amnezia::serialization::vless
|
||||
|
||||
const QString Serialize(const VlessServerObject &server, const QString &alias)
|
||||
{
|
||||
|
||||
QUrl url;
|
||||
|
||||
// Set basic URL components
|
||||
url.setScheme("vless");
|
||||
url.setUserInfo(server.id);
|
||||
url.setHost(server.address);
|
||||
url.setPort(server.port);
|
||||
|
||||
QUrlQuery query;
|
||||
|
||||
if (!server.network.isEmpty() && server.network != "tcp") {
|
||||
query.addQueryItem("type", server.network);
|
||||
}
|
||||
|
||||
if (!server.encryption.isEmpty()) {
|
||||
query.addQueryItem("encryption", server.encryption);
|
||||
}
|
||||
|
||||
if (!server.security.isEmpty() && server.security != "none") {
|
||||
query.addQueryItem("security", server.security);
|
||||
}
|
||||
|
||||
if (!server.flow.isEmpty() && (server.security == "xtls" || server.security == "reality")) {
|
||||
query.addQueryItem("flow", server.flow);
|
||||
}
|
||||
|
||||
if (!server.serverName.isEmpty()) {
|
||||
query.addQueryItem("sni", server.serverName);
|
||||
}
|
||||
|
||||
if (server.security == "reality") {
|
||||
if (!server.fingerprint.isEmpty()) {
|
||||
query.addQueryItem("fp", server.fingerprint);
|
||||
}
|
||||
|
||||
if (!server.publicKey.isEmpty()) {
|
||||
query.addQueryItem("pbk", server.publicKey);
|
||||
}
|
||||
|
||||
if (!server.shortId.isEmpty()) {
|
||||
query.addQueryItem("sid", server.shortId);
|
||||
}
|
||||
|
||||
if (!server.spiderX.isEmpty()) {
|
||||
query.addQueryItem("spiderX", server.spiderX);
|
||||
}
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
|
||||
if (!alias.isEmpty()) {
|
||||
url.setFragment(alias);
|
||||
}
|
||||
|
||||
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
124
client/core/webview/CMakeLists.txt
Normal file
124
client/core/webview/CMakeLists.txt
Normal file
@@ -0,0 +1,124 @@
|
||||
get_filename_component(DIR_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
||||
message("Configuring " ${DIR_NAME})
|
||||
|
||||
set(webview_URI AmneziaWebView)
|
||||
|
||||
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Quick)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick)
|
||||
|
||||
# Widgets and WebEngineWidgets are only available on desktop platforms
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS WebEngineWidgets)
|
||||
endif()
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
set(PLUGIN_CLASS_NAME WebViewPlugin)
|
||||
add_definitions(-DURI=${webview_URI})
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set(webview_HEADERS
|
||||
amneziawebview.h
|
||||
amneziawebview_p.h
|
||||
websettings.h
|
||||
mimecache.h
|
||||
filehandler.h
|
||||
qrchandler.h
|
||||
jshandler.h
|
||||
plugin.h
|
||||
amneziawebhistory.h
|
||||
amneziawebhistory_p.h
|
||||
)
|
||||
|
||||
set(webview_SOURCES
|
||||
amneziawebview.cpp
|
||||
amneziawebview_p.cpp
|
||||
websettings.cpp
|
||||
mimecache.cpp
|
||||
qrchandler.cpp
|
||||
jshandler.cpp
|
||||
filehandler.cpp
|
||||
plugin.cpp
|
||||
amneziawebhistory.cpp
|
||||
)
|
||||
|
||||
if (CMAKE_CROSSCOMPILING AND ANDROID)
|
||||
|
||||
list(APPEND webview_SOURCES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/jshandler_android.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/qrchandler_android.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/filehandler_android.cpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/amneziawebview_android.cpp"
|
||||
)
|
||||
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CROSSCOMPILING AND APPLE)
|
||||
|
||||
add_definitions(-DENABLE_WKWEBVIEW)
|
||||
list(APPEND webview_SOURCES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/amneziawebview_ios.mm"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/qrchandler_ios.mm"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/jshandler_ios.mm"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/filehandler_ios.mm"
|
||||
)
|
||||
|
||||
endif ()
|
||||
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
# Require WebEngineWidgets for desktop platforms (QtWebKit is not available in Qt 6)
|
||||
if (Qt6WebEngineWidgets_FOUND)
|
||||
message(STATUS "Using Qt WebEngineWidgets for desktop webview")
|
||||
list(APPEND webview_HEADERS
|
||||
amneziawebview_webengine_p.h
|
||||
)
|
||||
list(APPEND webview_SOURCES
|
||||
amneziawebview_webengine.cpp
|
||||
)
|
||||
else ()
|
||||
message(FATAL_ERROR "Qt WebEngineWidgets is required for desktop builds. QtWebKit is not available in Qt 6. Please install Qt WebEngineWidgets module.")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
add_library(webview STATIC ${webview_SOURCES} ${webview_HEADERS})
|
||||
|
||||
target_compile_definitions(webview PRIVATE
|
||||
QT_PLUGIN
|
||||
QT_STATICPLUGIN
|
||||
)
|
||||
|
||||
target_link_libraries(webview PUBLIC
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
)
|
||||
|
||||
# Widgets and WebEngineWidgets are only available on desktop platforms
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
if(TARGET Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
target_link_libraries(webview PUBLIC Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
endif()
|
||||
|
||||
if (Qt6WebEngineWidgets_FOUND)
|
||||
target_link_libraries(webview PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::WebEngineWidgets
|
||||
)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# Link WebKit framework for iOS
|
||||
if (CMAKE_CROSSCOMPILING AND APPLE)
|
||||
find_library(FW_WEBKIT WebKit)
|
||||
if(FW_WEBKIT)
|
||||
target_link_libraries(webview PRIVATE ${FW_WEBKIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set_target_properties(webview PROPERTIES AUTOMOC_MOC_OPTIONS "-Muri=${webview_URI}")
|
||||
|
||||
#include(precompiled.headers)
|
||||
#add_precompiled_header(webview pch.h FORCEINCLUDE)
|
||||
434
client/core/webview/amneziawebhistory.cpp
Normal file
434
client/core/webview/amneziawebhistory.cpp
Normal file
@@ -0,0 +1,434 @@
|
||||
#include "amneziawebhistory.h"
|
||||
#include "amneziawebhistory_p.h"
|
||||
#include "amneziawebview.h"
|
||||
#include "amneziawebview_p.h"
|
||||
|
||||
#include <QSharedData>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
/*!
|
||||
Constructs a history item from \a other. The new item and \a other
|
||||
will share their data, and modifying either this item or \a other will
|
||||
modify both instances.
|
||||
*/
|
||||
AmneziaWebHistoryItem::AmneziaWebHistoryItem(const AmneziaWebHistoryItem &other)
|
||||
: d_ptr(other.d_ptr)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Assigns the \a other history item to this. This item and \a other
|
||||
will share their data, and modifying either this item or \a other will
|
||||
modify both instances.
|
||||
*/
|
||||
AmneziaWebHistoryItem &AmneziaWebHistoryItem::operator=(const AmneziaWebHistoryItem &other)
|
||||
{
|
||||
d_ptr = other.d_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*!
|
||||
Destroys the history item.
|
||||
*/
|
||||
AmneziaWebHistoryItem::~AmneziaWebHistoryItem()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the URL associated with the history item.
|
||||
|
||||
\sa originalUrl(), title(), lastVisited(), data(), mimeType()
|
||||
*/
|
||||
QUrl AmneziaWebHistoryItem::url() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistoryItem);
|
||||
if (d)
|
||||
return d->url();
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the title of the page associated with the history item.
|
||||
|
||||
\sa icon(), url(), lastVisited(), data(), mimeType()
|
||||
*/
|
||||
QString AmneziaWebHistoryItem::title() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistoryItem);
|
||||
if (d)
|
||||
return d->title();
|
||||
return QString();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the icon associated with the history item.
|
||||
|
||||
\sa title(), url(), lastVisited(), data(), mimeType()
|
||||
*/
|
||||
QIcon AmneziaWebHistoryItem::icon() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistoryItem);
|
||||
if (d)
|
||||
return d->icon();
|
||||
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the data associated with the history item.
|
||||
|
||||
\sa icon(), title(), url(), lastVisited(), mimeType()
|
||||
*/
|
||||
QByteArray AmneziaWebHistoryItem::data() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistoryItem);
|
||||
if(d) return d->data();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the mimeType associated with the history item.
|
||||
|
||||
\sa icon(), title(), url(), lastVisited(), data()
|
||||
*/
|
||||
QString AmneziaWebHistoryItem::mimeType() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistoryItem);
|
||||
if(d) return d->mimeType();
|
||||
return QString("text/html");
|
||||
}
|
||||
|
||||
/*!*
|
||||
\internal
|
||||
*/
|
||||
AmneziaWebHistoryItem::AmneziaWebHistoryItem(AmneziaWebHistoryItemPrivate *priv) : d_ptr(priv)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 4.5
|
||||
Returns whether this is a valid history item.
|
||||
*/
|
||||
bool AmneziaWebHistoryItem::isValid() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistoryItem);
|
||||
bool valid = (d);
|
||||
return valid;
|
||||
}
|
||||
|
||||
AmneziaWebHistory::AmneziaWebHistory(AmneziaWebView *parent) : QObject(parent)
|
||||
, d_ptr(new AmneziaWebHistoryPrivate())
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
d->q_ptr = this;
|
||||
}
|
||||
|
||||
AmneziaWebHistory::~AmneziaWebHistory()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
Clears the history.
|
||||
|
||||
\sa count(), items()
|
||||
*/
|
||||
void AmneziaWebHistory::clear()
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
while (d->items.count()) {
|
||||
d->items.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a list of all items currently in the history.
|
||||
|
||||
\sa count(), clear()
|
||||
*/
|
||||
QList<AmneziaWebHistoryItem> AmneziaWebHistory::items() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
QList<AmneziaWebHistoryItem> ret;
|
||||
|
||||
for (int i = 0; i < d->items.size(); ++i) {
|
||||
AmneziaWebHistoryItem item(d->items[i]);
|
||||
ret.append(item);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the list of items in the backwards history list.
|
||||
At most \a maxItems entries are returned.
|
||||
|
||||
\sa forwardItems()
|
||||
*/
|
||||
QList<AmneziaWebHistoryItem> AmneziaWebHistory::backItems(int maxItems) const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
|
||||
int count = d->currentIndex;
|
||||
if (maxItems >= 0) {
|
||||
count = qMin(count, maxItems);
|
||||
}
|
||||
|
||||
QList<AmneziaWebHistoryItem> ret;
|
||||
for (int i = (d->currentIndex - count); i < d->currentIndex; i++) {
|
||||
ret.append(d->items[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the list of items in the forward history list.
|
||||
At most \a maxItems entries are returned.
|
||||
|
||||
\sa backItems()
|
||||
*/
|
||||
QList<AmneziaWebHistoryItem> AmneziaWebHistory::forwardItems(int maxItems) const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
|
||||
int count = d->items.count() - d->currentIndex - 1;
|
||||
if (maxItems >= 0) {
|
||||
count = qMin(count, maxItems);
|
||||
}
|
||||
|
||||
QList<AmneziaWebHistoryItem> ret;
|
||||
for (int i = (d->currentIndex + 1); i <= d->currentIndex + count; i++) {
|
||||
ret.append(d->items[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns true if there is an item preceding the current item in the history;
|
||||
otherwise returns false.
|
||||
|
||||
\sa canGoForward()
|
||||
*/
|
||||
bool AmneziaWebHistory::canGoBack() const
|
||||
{
|
||||
const AmneziaWebHistoryItem current = currentItem();
|
||||
bool can = (current.isValid() && current.d_ptr->backItem() != nullptr);
|
||||
return can;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns true if we have an item to go forward to; otherwise returns false.
|
||||
|
||||
\sa canGoBack()
|
||||
*/
|
||||
bool AmneziaWebHistory::canGoForward() const
|
||||
{
|
||||
const AmneziaWebHistoryItem current = currentItem();
|
||||
bool can = (current.isValid() && current.d_ptr->forwardItem() != nullptr);
|
||||
return can;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the current item to be the previous item in the history and goes to the
|
||||
corresponding page; i.e., goes back one history item.
|
||||
|
||||
\sa forward(), goToItem()
|
||||
*/
|
||||
void AmneziaWebHistory::back()
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
if(!canGoBack()) return;
|
||||
AmneziaWebView *view = qobject_cast<AmneziaWebView*>(parent());
|
||||
AmneziaWebHistoryItem item = backItem();
|
||||
|
||||
d->currentIndex--;
|
||||
if (view) {
|
||||
if (item.data().length() > 0) {
|
||||
view->setContent(item.data(), item.mimeType(), item.url());
|
||||
}
|
||||
else {
|
||||
|
||||
view->setUrl(item.url());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current item to be the next item in the history and goes to the
|
||||
corresponding page; i.e., goes forward one history item.
|
||||
|
||||
\sa back(), goToItem()
|
||||
*/
|
||||
void AmneziaWebHistory::forward()
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
if(!canGoForward()) return;
|
||||
AmneziaWebView *view = qobject_cast<AmneziaWebView*>(parent());
|
||||
AmneziaWebHistoryItem item = backItem();
|
||||
|
||||
d->currentIndex++;
|
||||
if (view) {
|
||||
if (item.data().length() > 0) {
|
||||
view->setContent(item.data(), item.mimeType(), item.url());
|
||||
}
|
||||
else {
|
||||
view->setUrl(item.url());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current item to be the specified \a item in the history and goes to the page.
|
||||
|
||||
\sa back(), forward()
|
||||
*/
|
||||
void AmneziaWebHistory::goToItem(const AmneziaWebHistoryItem &item)
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
if(!item.isValid()) return;
|
||||
AmneziaWebView *view = qobject_cast<AmneziaWebView*>(parent());
|
||||
if (!view) return; //There is no view to go.
|
||||
if (item.url().isEmpty()) return; //
|
||||
|
||||
int index = -1;
|
||||
for(int i= 0; i < d->items.count(); ++i) {
|
||||
if(d->items[i].d_ptr.data() == item.d_ptr.data()) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
|
||||
d->currentIndex = index;
|
||||
if (item.data().length() > 0) {
|
||||
view->setContent(item.data(), item.mimeType(), item.url());
|
||||
}
|
||||
else {
|
||||
view->setUrl(item.url());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current item in the history.
|
||||
*/
|
||||
AmneziaWebHistoryItem AmneziaWebHistory::currentItem() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
|
||||
if ((d->currentIndex >= 0) && (d->currentIndex < d->items.count())) {
|
||||
return AmneziaWebHistoryItem(d->items.at(d->currentIndex));
|
||||
}
|
||||
return AmneziaWebHistoryItem(nullptr);
|
||||
}
|
||||
|
||||
void AmneziaWebHistory::append(const QUrl& url, const QByteArray& data, const QString& mimeType)
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
|
||||
const AmneziaWebHistoryItem current = currentItem();
|
||||
// Check if url is same as current, and do not add it second time.
|
||||
if (current.url() == url) return;
|
||||
|
||||
AmneziaWebHistoryItemPrivate *priv = new AmneziaWebHistoryItemPrivate();
|
||||
if(current.isValid()) {
|
||||
current.d_ptr->_forwardItem = priv;
|
||||
priv->_backItem = current.d_ptr.data();
|
||||
}
|
||||
priv->_data = data;
|
||||
priv->_url = url;
|
||||
priv->_mimeType = mimeType;
|
||||
|
||||
//Remove last items till current
|
||||
while (d->items.count() > (d->currentIndex + 1)) {
|
||||
d->items.removeLast();
|
||||
}
|
||||
|
||||
//No more then maximum
|
||||
while (d->items.count() >= d->maximumCount) {
|
||||
d->items.removeFirst();
|
||||
}
|
||||
|
||||
d->items.append(AmneziaWebHistoryItem(priv));
|
||||
d->currentIndex = (d->items.count() - 1);
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item before the current item in the history.
|
||||
*/
|
||||
AmneziaWebHistoryItem AmneziaWebHistory::backItem() const
|
||||
{
|
||||
AmneziaWebHistoryItem current = currentItem();
|
||||
return AmneziaWebHistoryItem(current.d_ptr->backItem());
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the item after the current item in the history.
|
||||
*/
|
||||
AmneziaWebHistoryItem AmneziaWebHistory::forwardItem() const
|
||||
{
|
||||
AmneziaWebHistoryItem current = currentItem();
|
||||
return AmneziaWebHistoryItem(current.d_ptr->forwardItem());
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 4.5
|
||||
Returns the index of the current item in history.
|
||||
*/
|
||||
int AmneziaWebHistory::currentItemIndex() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
return d->currentIndex;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item at index \a i in the history.
|
||||
*/
|
||||
AmneziaWebHistoryItem AmneziaWebHistory::itemAt(int i) const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
int index = (i < 0) ? 0 : i;
|
||||
index = (index >= count()) ? (count() -1) : index;
|
||||
if (index >= 0) {
|
||||
return AmneziaWebHistoryItem(d->items.at(index));
|
||||
}
|
||||
return AmneziaWebHistoryItem(nullptr);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the total number of items in the history.
|
||||
*/
|
||||
int AmneziaWebHistory::count() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
return d->items.count();
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 4.5
|
||||
Returns the maximum number of items in the history.
|
||||
|
||||
\sa setMaximumItemCount()
|
||||
*/
|
||||
int AmneziaWebHistory::maximumItemCount() const
|
||||
{
|
||||
Q_D(const AmneziaWebHistory);
|
||||
return d->maximumCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 4.5
|
||||
Sets the maximum number of items in the history to \a count.
|
||||
|
||||
\sa maximumItemCount()
|
||||
*/
|
||||
void AmneziaWebHistory::setMaximumItemCount(int count)
|
||||
{
|
||||
Q_D(AmneziaWebHistory);
|
||||
d->maximumCount = count;
|
||||
}
|
||||
78
client/core/webview/amneziawebhistory.h
Normal file
78
client/core/webview/amneziawebhistory.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef WEBHISTORY_H
|
||||
#define WEBHISTORY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QIcon>
|
||||
|
||||
class AmneziaWebViewPrivate;
|
||||
class AmneziaWebView;
|
||||
|
||||
class AmneziaWebHistory;
|
||||
class AmneziaWebHistoryItemPrivate;
|
||||
class AmneziaWebHistoryItem
|
||||
{
|
||||
Q_DECLARE_PRIVATE(AmneziaWebHistoryItem)
|
||||
public:
|
||||
|
||||
AmneziaWebHistoryItem(const AmneziaWebHistoryItem &other);
|
||||
AmneziaWebHistoryItem &operator=(const AmneziaWebHistoryItem &other);
|
||||
~AmneziaWebHistoryItem();
|
||||
|
||||
QUrl url() const;
|
||||
QString title() const;
|
||||
QIcon icon() const;
|
||||
|
||||
QByteArray data() const;
|
||||
QString mimeType() const;
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
explicit AmneziaWebHistoryItem(AmneziaWebHistoryItemPrivate *priv);
|
||||
friend class AmneziaWebHistory;
|
||||
friend class AmneziaWebViewPrivate;
|
||||
QExplicitlySharedDataPointer<AmneziaWebHistoryItemPrivate> d_ptr;
|
||||
};
|
||||
|
||||
class AmneziaWebHistoryPrivate;
|
||||
class AmneziaWebHistory : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(AmneziaWebHistory)
|
||||
|
||||
public:
|
||||
virtual ~AmneziaWebHistory();
|
||||
|
||||
void append(const QUrl& url, const QByteArray& data = QByteArray(), const QString& mimeType = QString("text/html"));
|
||||
void clear();
|
||||
|
||||
QList<AmneziaWebHistoryItem> items() const;
|
||||
QList<AmneziaWebHistoryItem> backItems(int maxItems) const;
|
||||
QList<AmneziaWebHistoryItem> forwardItems(int maxItems) const;
|
||||
|
||||
bool canGoBack() const;
|
||||
bool canGoForward() const;
|
||||
|
||||
void back();
|
||||
void forward();
|
||||
void goToItem(const AmneziaWebHistoryItem &item);
|
||||
|
||||
AmneziaWebHistoryItem backItem() const;
|
||||
AmneziaWebHistoryItem currentItem() const;
|
||||
AmneziaWebHistoryItem forwardItem() const;
|
||||
AmneziaWebHistoryItem itemAt(int i) const;
|
||||
int currentItemIndex() const;
|
||||
|
||||
int count() const;
|
||||
int maximumItemCount() const;
|
||||
void setMaximumItemCount(int count);
|
||||
|
||||
private:
|
||||
|
||||
friend class AmneziaWebViewPrivate;
|
||||
explicit AmneziaWebHistory(AmneziaWebView *parent);
|
||||
Q_DISABLE_COPY(AmneziaWebHistory)
|
||||
QScopedPointer<AmneziaWebHistoryPrivate> d_ptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
72
client/core/webview/amneziawebhistory_p.h
Normal file
72
client/core/webview/amneziawebhistory_p.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef WEBHISTORY_P_H
|
||||
#define WEBHISTORY_P_H
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include "amneziawebhistory.h"
|
||||
|
||||
class AmneziaWebHistoryItemPrivate;
|
||||
class AmneziaWebHistoryItem;
|
||||
|
||||
class AmneziaWebHistoryPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(AmneziaWebHistory)
|
||||
public:
|
||||
|
||||
static AmneziaWebHistoryPrivate *get(AmneziaWebHistory *q)
|
||||
{
|
||||
if (!q) { return nullptr; }
|
||||
return q->d_func();
|
||||
}
|
||||
|
||||
AmneziaWebHistoryPrivate(): currentIndex(-1), maximumCount(10), q_ptr(nullptr) { }
|
||||
~AmneziaWebHistoryPrivate() = default;
|
||||
|
||||
|
||||
private:
|
||||
friend class AmneziaWebHistoryItemPrivate;
|
||||
int currentIndex;
|
||||
int maximumCount;
|
||||
QList<AmneziaWebHistoryItem> items;
|
||||
AmneziaWebHistory *q_ptr;
|
||||
};
|
||||
|
||||
class AmneziaWebHistoryItemPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
|
||||
static QExplicitlySharedDataPointer<AmneziaWebHistoryItemPrivate> get(AmneziaWebHistoryItem *q)
|
||||
{
|
||||
return q->d_ptr;
|
||||
}
|
||||
|
||||
~AmneziaWebHistoryItemPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
QUrl url() const { return _url; }
|
||||
QString title() const { return _title; }
|
||||
QIcon icon() const {return _icon;}
|
||||
QByteArray data() const {return _data;}
|
||||
QString mimeType() const {return _mimeType;}
|
||||
|
||||
// Every item knows its back and forward items
|
||||
AmneziaWebHistoryItemPrivate *backItem() {return _backItem; }
|
||||
AmneziaWebHistoryItemPrivate *forwardItem() {return _forwardItem; }
|
||||
|
||||
private:
|
||||
friend class AmneziaWebHistory;
|
||||
AmneziaWebHistoryItemPrivate() = default;
|
||||
|
||||
AmneziaWebHistoryItemPrivate *_backItem = nullptr;
|
||||
AmneziaWebHistoryItemPrivate *_forwardItem = nullptr;
|
||||
QIcon _icon;
|
||||
QString _title;
|
||||
QUrl _url;
|
||||
QString _html;
|
||||
QByteArray _data;
|
||||
QString _mimeType;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
751
client/core/webview/amneziawebview.cpp
Normal file
751
client/core/webview/amneziawebview.cpp
Normal file
@@ -0,0 +1,751 @@
|
||||
#include <QDebug>
|
||||
#include <QEvent>
|
||||
#include <QFile>
|
||||
#include <QThread>
|
||||
#include <QMetaObject>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QPen>
|
||||
#include <QList>
|
||||
#include <QQuickWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
#include <QGuiApplication>
|
||||
#define qApp qGuiApp
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "amneziawebview.h"
|
||||
#include "amneziawebview_p.h"
|
||||
|
||||
QUrl defaultBaseUrl()
|
||||
{
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS)
|
||||
return QUrl(QLatin1String("local:///"));
|
||||
#else
|
||||
return QUrl(QLatin1String("file:///"));
|
||||
#endif
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
AmneziaWebView::AmneziaWebView(QQuickItem *parent) : QQuickPaintedItem(parent),
|
||||
d_ptr(AmneziaWebViewPrivate::create(this))
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->q_ptr = this;
|
||||
d->init();
|
||||
init();
|
||||
}
|
||||
|
||||
AmneziaWebView::~AmneziaWebView()
|
||||
{
|
||||
disconnect(this);
|
||||
}
|
||||
|
||||
void AmneziaWebView::init()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
setFlag(QQuickItem::ItemHasContents, true);
|
||||
setOpaquePainting(true);
|
||||
|
||||
setClip(true);
|
||||
|
||||
connect(this, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(windowWasChanged(QQuickWindow*)));
|
||||
connect(this, SIGNAL(parentChanged(QQuickItem*)), this, SLOT(parentWasChanged()));
|
||||
connect(this, SIGNAL(fillColorChanged()), this,SLOT(fillColorWasChanged()));
|
||||
|
||||
connect(d, SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged(QString)));
|
||||
connect(d, SIGNAL(loadStarted()), this, SLOT(doLoadStarted()));
|
||||
connect(d, SIGNAL(loadFinished(bool)), this, SLOT(doLoadFinished(bool)));
|
||||
}
|
||||
|
||||
void AmneziaWebView::componentComplete()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
QQuickItem::componentComplete();
|
||||
|
||||
// Update geometry after component is complete
|
||||
if (window()) {
|
||||
QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
switch (d->pending) {
|
||||
case AmneziaWebViewPrivate::PendingUrl:
|
||||
// Make WebView visible before loading
|
||||
if (isVisible() && !d->visible) {
|
||||
d->show();
|
||||
}
|
||||
setUrl(d->pendingUrl);
|
||||
break;
|
||||
case AmneziaWebViewPrivate::PendingHtml:
|
||||
if (isVisible() && !d->visible) {
|
||||
d->show();
|
||||
}
|
||||
setHtml(d->pendingString, d->pendingUrl);
|
||||
break;
|
||||
case AmneziaWebViewPrivate::PendingContent:
|
||||
if (isVisible() && !d->visible) {
|
||||
d->show();
|
||||
}
|
||||
setContent(d->pendingData, d->pendingString, d->pendingUrl);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AmneziaWebView::Status AmneziaWebView::status() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->status;
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty real WebView::progress
|
||||
This property holds the progress of loading the current URL, from 0 to 1.
|
||||
|
||||
If you just want to know when progress gets to 1, use
|
||||
WebView::onLoadFinished() or WebView::onLoadFailed() instead.
|
||||
*/
|
||||
qreal AmneziaWebView::progress() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->progress;
|
||||
}
|
||||
|
||||
void AmneziaWebView::doLoadStarted()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
if (!d->url.isEmpty()) {
|
||||
d->status = Loading;
|
||||
emit statusChanged(d->status);
|
||||
}
|
||||
emit loadStarted();
|
||||
}
|
||||
|
||||
void AmneziaWebView::doLoadProgress(int p)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
if (d->progress == p / 100.0)
|
||||
return;
|
||||
d->progress = p / 100.0;
|
||||
emit progressChanged();
|
||||
}
|
||||
|
||||
|
||||
void AmneziaWebView::doLoadFinished(bool ok)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
if (ok) {
|
||||
d->status = d->url.isEmpty() ? Null : Ready;
|
||||
emit loadFinished();
|
||||
} else {
|
||||
d->status = Error;
|
||||
emit loadFailed();
|
||||
}
|
||||
emit statusChanged(d->status);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty url AmneziaWebView::url
|
||||
This property holds the URL to the page displayed in this item. It can be set,
|
||||
but also can change spontaneously (eg. because of network redirection).
|
||||
|
||||
If the url is empty, the page is blank.
|
||||
|
||||
The url is always absolute (QML will resolve relative URL strings in the context
|
||||
of the containing QML document).
|
||||
*/
|
||||
QUrl AmneziaWebView::url() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->url;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setUrl(const QUrl& url)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
QString urlString = url.toString();
|
||||
while ( urlString.endsWith('#')) urlString.chop(1);
|
||||
QUrl newUrl(urlString);
|
||||
|
||||
if (newUrl == QUrl(QLatin1String("about:blank")) ) {
|
||||
newUrl = QUrl("");
|
||||
}
|
||||
|
||||
if ((url == d->url) || (newUrl == d->url))
|
||||
return;
|
||||
|
||||
if (isComponentComplete()) {
|
||||
// Make WebView visible before loading
|
||||
if (isVisible() && !d->visible) {
|
||||
d->show();
|
||||
}
|
||||
d->load(url);
|
||||
|
||||
} else {
|
||||
|
||||
d->pending = d->PendingUrl;
|
||||
d->pendingUrl = url;
|
||||
}
|
||||
}
|
||||
|
||||
qreal AmneziaWebView::preferredWidth() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->preferredwidth;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setPreferredWidth(qreal width)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
if (d->preferredwidth == width)
|
||||
return;
|
||||
|
||||
d->preferredwidth = width;
|
||||
updateContentsSize();
|
||||
setImplicitWidth(width);
|
||||
emit preferredWidthChanged();
|
||||
}
|
||||
|
||||
qreal AmneziaWebView::preferredHeight() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->preferredheight;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setPreferredHeight(qreal height)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
if (d->preferredheight == height)
|
||||
return;
|
||||
|
||||
d->preferredheight = height;
|
||||
updateContentsSize();
|
||||
setImplicitHeight(height);
|
||||
emit preferredHeightChanged();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\qmlmethod bool AmneziaWebView::evaluateJavaScript(string scriptSource)
|
||||
|
||||
Evaluates the \a scriptSource JavaScript inside the context of the
|
||||
main web frame, and returns the result of the last executed statement.
|
||||
|
||||
Note that this JavaScript does \e not have any access to QML objects
|
||||
except as made available as windowObjects.
|
||||
*/
|
||||
void AmneziaWebView::evaluateJavaScript(const QString& scriptSource)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
if (qApp->thread() == QThread::currentThread()) {
|
||||
d->evaluateJavaScript(scriptSource);
|
||||
}
|
||||
else {
|
||||
QMetaObject::invokeMethod(d, "evaluateJavaScript", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString, scriptSource));
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaWebView::windowWasChanged(QQuickWindow* window)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->setWindowParent(window);
|
||||
}
|
||||
|
||||
void AmneziaWebView::updateGeometry()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
QRectF geometry = QRectF(QPointF(x(), y()), QSizeF(width(), height()));
|
||||
if (!geometry.isEmpty() && window()) {
|
||||
QRectF sceneGeometry = mapRectToScene(geometry);
|
||||
QRect rect = sceneGeometry.toRect();
|
||||
qDebug() << "AmneziaWebView::updateGeometry() - local:" << geometry
|
||||
<< "scene:" << sceneGeometry << "rect:" << rect
|
||||
<< "width:" << width() << "height:" << height();
|
||||
d->setGeometry(rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AmneziaWebView::parentWasChanged()
|
||||
{
|
||||
if (parentItem()) {
|
||||
updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QQuickItem *> recurseChildren(QQuickItem * parentItem)
|
||||
{
|
||||
QList<QQuickItem *>childs = parentItem->childItems();
|
||||
QList<QQuickItem *> items;
|
||||
int count = childs.count();
|
||||
for(int i = count - 1; i >= 0; --i) {
|
||||
QQuickItem *next = childs.at(i);
|
||||
items.append(recurseChildren(next));
|
||||
}
|
||||
items.append(childs);
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
void AmneziaWebView::paint(QPainter *painter)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
if (!painter || !window() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
QRectF contentRect = contentsBoundingRect();
|
||||
if ((contentRect.height() <= 0) || (contentRect.width() <= 0)) return;
|
||||
|
||||
painter->setOpacity(1.0);
|
||||
QMutexLocker lock(&d->renderMutex);
|
||||
|
||||
QColor color = d->backgroundColor;
|
||||
painter->fillRect(contentRect, color);
|
||||
|
||||
}
|
||||
|
||||
void AmneziaWebView::afterRendering()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
Qt::ApplicationState state = qApp->applicationState();
|
||||
if ( state != Qt::ApplicationActive) {
|
||||
if (d->visible && isVisible()) {
|
||||
QMetaObject::invokeMethod(d, "requestHide", Qt::QueuedConnection);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window()) return;
|
||||
|
||||
if (isVisible()) {
|
||||
QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(d, "requestShow", Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void AmneziaWebView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
|
||||
|
||||
// Update WebView geometry when QML item size changes
|
||||
if (window() && !newGeometry.isEmpty() && newGeometry != oldGeometry) {
|
||||
QMetaObject::invokeMethod(this, "updateGeometry", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaWebView::itemChange(ItemChange change, const ItemChangeData & value)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
switch (change) {
|
||||
case ItemSceneChange: {
|
||||
QQuickWindow *sc = value.window;
|
||||
if (sc) {
|
||||
connect(sc, SIGNAL(afterRendering()), this, SLOT(afterRendering()), Qt::QueuedConnection);
|
||||
}
|
||||
else {
|
||||
disconnect(this, SLOT(afterRendering()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ItemVisibleHasChanged: {
|
||||
|
||||
if (!window()) break;
|
||||
|
||||
if (value.boolValue) {
|
||||
// Component became visible - show WebView
|
||||
if (!d->visible) {
|
||||
d->show();
|
||||
}
|
||||
} else {
|
||||
QMetaObject::invokeMethod(d, "requestHide", Qt::QueuedConnection);
|
||||
}
|
||||
if (value.boolValue && !d->overlapped) {
|
||||
QMetaObject::invokeMethod(d, "requestShow", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
QQuickPaintedItem::itemChange(change, value);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty list<object> WebView::javaScriptWindowObjects
|
||||
|
||||
A list of QML objects to expose to the web page.
|
||||
|
||||
Each object will be added as a property of the web frame's window object. The
|
||||
property name is controlled by the value of \c WebView.windowObjectName
|
||||
attached property.
|
||||
|
||||
Exposing QML objects to a web page allows JavaScript executing in the web
|
||||
page itself to communicate with QML, by reading and writing properties and
|
||||
by calling methods of the exposed QML objects.
|
||||
|
||||
This example shows how to call into a QML method using a window object.
|
||||
|
||||
\qml
|
||||
WebView {
|
||||
javaScriptWindowObjects: QtObject {
|
||||
WebView.windowObjectName: "qml"
|
||||
|
||||
function qmlCall() {
|
||||
console.log("This call is in QML!");
|
||||
}
|
||||
}
|
||||
|
||||
html: "<script>window.qml.qmlCall();</script>"
|
||||
}
|
||||
\endqml
|
||||
|
||||
The output of the example will be:
|
||||
\code
|
||||
This call is in QML!
|
||||
\endcode
|
||||
|
||||
If Javascript is not enabled for the page, then this property does nothing.
|
||||
*/
|
||||
QQmlListProperty<QObject> AmneziaWebView::javaScriptWindowObjects()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
return QQmlListProperty<QObject>(this, d, &AmneziaWebViewPrivate::windowObjectsAppend,
|
||||
&AmneziaWebViewPrivate::windowObjectsCount,
|
||||
&AmneziaWebViewPrivate::windowObjectsAt,
|
||||
&AmneziaWebViewPrivate::windowObjectsClear );
|
||||
}
|
||||
|
||||
AmneziaWebViewSettings* AmneziaWebView::settingsObject() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->m_settings.data();
|
||||
}
|
||||
|
||||
|
||||
AmneziaWebViewAttached* AmneziaWebView::qmlAttachedProperties(QObject* o)
|
||||
{
|
||||
return new AmneziaWebViewAttached(o);
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::updateWindowObjects()
|
||||
{
|
||||
|
||||
if (!q_ptr->isComponentCompletePublic())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < windowObjects.count(); ++i) {
|
||||
QObject* object = windowObjects.at(i);
|
||||
AmneziaWebViewAttached* attached = static_cast<AmneziaWebViewAttached *>(qmlAttachedPropertiesObject<AmneziaWebView>(object));
|
||||
if (attached && !attached->windowObjectName().isEmpty())
|
||||
addToJavaScriptWindowObject(attached->windowObjectName(), object);
|
||||
}
|
||||
}
|
||||
|
||||
int AmneziaWebView::pressGrabTime() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setPressGrabTime(int millis)
|
||||
{
|
||||
Q_UNUSED(millis)
|
||||
|
||||
emit pressGrabTimeChanged();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_ACTION
|
||||
/*!
|
||||
\qmlproperty action WebView::back
|
||||
This property holds the action for causing the previous URL in the history to be displayed.
|
||||
*/
|
||||
QAction* AmneziaWebView::backAction() const
|
||||
{
|
||||
return action(AmneziaWebView::Back);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty action WebView::forward
|
||||
This property holds the action for causing the next URL in the history to be displayed.
|
||||
*/
|
||||
QAction* AmneziaWebView::forwardAction() const
|
||||
{
|
||||
return action(AmneziaWebView::Forward);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty action WebView::reload
|
||||
This property holds the action for reloading with the current URL
|
||||
*/
|
||||
QAction* AmneziaWebView::reloadAction() const
|
||||
{
|
||||
return action(AmneziaWebView::Reload);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty action WebView::stop
|
||||
This property holds the action for stopping loading with the current URL
|
||||
*/
|
||||
QAction* AmneziaWebView::stopAction() const
|
||||
{
|
||||
return action(AmneziaWebView::Stop);
|
||||
}
|
||||
#endif // QT_NO_ACTION
|
||||
|
||||
/*!
|
||||
\qmlproperty string WebView::title
|
||||
This property holds the title of the web page currently viewed
|
||||
|
||||
By default, this property contains an empty string.
|
||||
*/
|
||||
QString AmneziaWebView::title() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->title;
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty pixmap WebView::icon
|
||||
This property holds the icon associated with the web page currently viewed
|
||||
*/
|
||||
QPixmap AmneziaWebView::icon() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->icon().pixmap(QSize(256, 256));
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty string WebView::statusText
|
||||
|
||||
This property is the current status suggested by the current web page. In a web browser,
|
||||
such status is often shown in some kind of status bar.
|
||||
*/
|
||||
void AmneziaWebView::setStatusText(const QString& text)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->statusText = text;
|
||||
emit statusTextChanged();
|
||||
}
|
||||
|
||||
void AmneziaWebView::windowObjectCleared()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->updateWindowObjects();
|
||||
}
|
||||
|
||||
QString AmneziaWebView::statusText() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->statusText;
|
||||
}
|
||||
|
||||
|
||||
void AmneziaWebView::load(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& body)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->load(request, operation, body);
|
||||
}
|
||||
|
||||
QString AmneziaWebView::html() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->toHtml();
|
||||
}
|
||||
|
||||
void AmneziaWebView::setHtml(const QString& html, const QUrl& baseUrl)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
auto originUrl = baseUrl.isValid() ? baseUrl : defaultBaseUrl();
|
||||
|
||||
updateContentsSize();
|
||||
if (isComponentComplete()) {
|
||||
d->setHtml(html, originUrl);
|
||||
}
|
||||
else {
|
||||
d->pending = d->PendingHtml;
|
||||
d->pendingUrl = originUrl;
|
||||
d->pendingString = html;
|
||||
}
|
||||
emit htmlChanged();
|
||||
}
|
||||
|
||||
void AmneziaWebView::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
|
||||
updateContentsSize();
|
||||
auto originUrl = baseUrl.isValid() ? baseUrl : defaultBaseUrl();
|
||||
|
||||
if (isComponentComplete())
|
||||
d->setContent(data, mimeType, qmlContext(this)->resolvedUrl(baseUrl));
|
||||
else {
|
||||
d->pending = d->PendingContent;
|
||||
d->pendingUrl = originUrl;
|
||||
d->pendingString = mimeType;
|
||||
d->pendingData = data;
|
||||
}
|
||||
}
|
||||
|
||||
AmneziaWebHistory* AmneziaWebView::history() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->history();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_ACTION
|
||||
QAction* AmneziaWebView::action(AmneziaWebView::WebAction action) const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->action(action);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
\qmlproperty component WebView::newWindowComponent
|
||||
|
||||
This property holds the component to use for new windows.
|
||||
The component must have a WebView somewhere in its structure.
|
||||
|
||||
When the web engine requests a new window, it will be an instance of
|
||||
this component.
|
||||
|
||||
The parent of the new window is set by newWindowParent. It must be set.
|
||||
*/
|
||||
QQmlComponent* AmneziaWebView::newWindowComponent() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->newWindowComponent;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setNewWindowComponent(QQmlComponent* newWindow)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
if (newWindow == d->newWindowComponent)
|
||||
return;
|
||||
d->newWindowComponent = newWindow;
|
||||
emit newWindowComponentChanged();
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\qmlproperty item WebView::newWindowParent
|
||||
|
||||
The parent item for new windows.
|
||||
|
||||
\sa newWindowComponent
|
||||
*/
|
||||
QQuickItem* AmneziaWebView::newWindowParent() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->newWindowParent;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setNewWindowParent(QQuickItem *parent)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
if (parent == d->newWindowParent)
|
||||
return;
|
||||
if (d->newWindowParent && parent) {
|
||||
QList<QQuickItem *> children = d->newWindowParent->childItems();
|
||||
for (int i = 0; i < children.count(); ++i)
|
||||
children.at(i)->setParentItem(parent);
|
||||
}
|
||||
d->newWindowParent = parent;
|
||||
emit newWindowParentChanged();
|
||||
}
|
||||
|
||||
QSize AmneziaWebView::contentsSize() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->contentsSize() * contentsScale();
|
||||
}
|
||||
|
||||
qreal AmneziaWebView::contentsScale() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->scale();
|
||||
}
|
||||
|
||||
void AmneziaWebView::setContentsScale(qreal scale)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
if (scale == d->scale())
|
||||
return;
|
||||
d->setScale(scale);
|
||||
|
||||
//updateGeometry();
|
||||
emit contentsScaleChanged();
|
||||
}
|
||||
|
||||
void AmneziaWebView::setDefaultFontSize(int size)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->setDefaultFontSize(size);
|
||||
}
|
||||
|
||||
void AmneziaWebView::setStandardFontFamily(const QString &family)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->setStandardFontFamily(family);
|
||||
}
|
||||
|
||||
void AmneziaWebView::setTextZoom(int percent)
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
d->setTextZoom(percent);
|
||||
}
|
||||
|
||||
|
||||
#ifdef Q_REVISION
|
||||
/*!
|
||||
\qmlproperty color WebView::backgroundColor
|
||||
\since QtWebKit 1.1
|
||||
This property holds the background color of the view.
|
||||
*/
|
||||
|
||||
QColor AmneziaWebView::backgroundColor() const
|
||||
{
|
||||
Q_D(const AmneziaWebView);
|
||||
return d->backgroundColor;
|
||||
}
|
||||
|
||||
void AmneziaWebView::setBackgroundColor(const QColor& color)
|
||||
{
|
||||
setFillColor(color);
|
||||
}
|
||||
|
||||
void AmneziaWebView::fillColorWasChanged()
|
||||
{
|
||||
Q_D(AmneziaWebView);
|
||||
QColor color = fillColor();
|
||||
d->setBackgroundColor(color);
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
309
client/core/webview/amneziawebview.h
Normal file
309
client/core/webview/amneziawebview.h
Normal file
@@ -0,0 +1,309 @@
|
||||
#ifndef DECLARATIVEWEBVIEW_H
|
||||
#define DECLARATIVEWEBVIEW_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
#include <QBasicTimer>
|
||||
#include <QUrl>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
|
||||
#include <QtQml>
|
||||
#include <QQuickPaintedItem>
|
||||
|
||||
#include "websettings.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class AmneziaWebViewSettings;
|
||||
class AmneziaWebViewPrivate;
|
||||
class AmneziaWebViewAttached;
|
||||
class AmneziaWebHistory;
|
||||
|
||||
class AmneziaWebView : public QQuickPaintedItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_ENUMS(Status SelectionMode)
|
||||
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
Q_PROPERTY(QPixmap icon READ icon NOTIFY iconChanged)
|
||||
Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
|
||||
Q_PROPERTY(QString html READ html WRITE setHtml NOTIFY htmlChanged)
|
||||
Q_PROPERTY(int pressGrabTime READ pressGrabTime WRITE setPressGrabTime NOTIFY pressGrabTimeChanged)
|
||||
Q_PROPERTY(qreal preferredWidth READ preferredWidth WRITE setPreferredWidth NOTIFY preferredWidthChanged)
|
||||
Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged)
|
||||
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
|
||||
Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
|
||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||
|
||||
|
||||
#ifndef QT_NO_ACTION
|
||||
Q_PROPERTY(QAction* reload READ reloadAction CONSTANT)
|
||||
Q_PROPERTY(QAction* back READ backAction CONSTANT)
|
||||
Q_PROPERTY(QAction* forward READ forwardAction CONSTANT)
|
||||
Q_PROPERTY(QAction* stop READ stopAction CONSTANT)
|
||||
#endif
|
||||
|
||||
Q_PROPERTY(AmneziaWebViewSettings* settings READ settingsObject CONSTANT)
|
||||
Q_PROPERTY(QQmlListProperty<QObject> javaScriptWindowObjects READ javaScriptWindowObjects CONSTANT)
|
||||
Q_PROPERTY(QQmlComponent* newWindowComponent READ newWindowComponent WRITE setNewWindowComponent NOTIFY newWindowComponentChanged)
|
||||
Q_PROPERTY(QQuickItem* newWindowParent READ newWindowParent WRITE setNewWindowParent NOTIFY newWindowParentChanged)
|
||||
Q_PROPERTY(QSize contentsSize READ contentsSize NOTIFY contentsSizeChanged)
|
||||
Q_PROPERTY(qreal contentsScale READ contentsScale WRITE setContentsScale NOTIFY contentsScaleChanged)
|
||||
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
|
||||
|
||||
public:
|
||||
|
||||
enum WebAction {
|
||||
NoWebAction = - 1,
|
||||
|
||||
OpenLink,
|
||||
|
||||
OpenLinkInNewWindow,
|
||||
OpenFrameInNewWindow,
|
||||
|
||||
DownloadLinkToDisk,
|
||||
CopyLinkToClipboard,
|
||||
|
||||
OpenImageInNewWindow,
|
||||
DownloadImageToDisk,
|
||||
CopyImageToClipboard,
|
||||
|
||||
Back,
|
||||
Forward,
|
||||
Stop,
|
||||
Reload,
|
||||
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
|
||||
Undo,
|
||||
Redo,
|
||||
MoveToNextChar,
|
||||
MoveToPreviousChar,
|
||||
MoveToNextWord,
|
||||
MoveToPreviousWord,
|
||||
MoveToNextLine,
|
||||
MoveToPreviousLine,
|
||||
MoveToStartOfLine,
|
||||
MoveToEndOfLine,
|
||||
MoveToStartOfBlock,
|
||||
MoveToEndOfBlock,
|
||||
MoveToStartOfDocument,
|
||||
MoveToEndOfDocument,
|
||||
SelectNextChar,
|
||||
SelectPreviousChar,
|
||||
SelectNextWord,
|
||||
SelectPreviousWord,
|
||||
SelectNextLine,
|
||||
SelectPreviousLine,
|
||||
SelectStartOfLine,
|
||||
SelectEndOfLine,
|
||||
SelectStartOfBlock,
|
||||
SelectEndOfBlock,
|
||||
SelectStartOfDocument,
|
||||
SelectEndOfDocument,
|
||||
DeleteStartOfWord,
|
||||
DeleteEndOfWord,
|
||||
|
||||
SetTextDirectionDefault,
|
||||
SetTextDirectionLeftToRight,
|
||||
SetTextDirectionRightToLeft,
|
||||
|
||||
ToggleBold,
|
||||
ToggleItalic,
|
||||
ToggleUnderline,
|
||||
|
||||
InspectElement,
|
||||
|
||||
InsertParagraphSeparator,
|
||||
InsertLineSeparator,
|
||||
|
||||
SelectAll,
|
||||
ReloadAndBypassCache,
|
||||
|
||||
PasteAndMatchStyle,
|
||||
RemoveFormat,
|
||||
|
||||
ToggleStrikethrough,
|
||||
ToggleSubscript,
|
||||
ToggleSuperscript,
|
||||
InsertUnorderedList,
|
||||
InsertOrderedList,
|
||||
Indent,
|
||||
Outdent,
|
||||
|
||||
AlignCenter,
|
||||
AlignJustified,
|
||||
AlignLeft,
|
||||
AlignRight,
|
||||
|
||||
StopScheduledPageRefresh,
|
||||
|
||||
CopyImageUrlToClipboard,
|
||||
|
||||
WebActionCount
|
||||
};
|
||||
|
||||
|
||||
explicit AmneziaWebView(QQuickItem *parent = nullptr);
|
||||
virtual ~AmneziaWebView();
|
||||
|
||||
QUrl url() const;
|
||||
void setUrl(const QUrl &);
|
||||
|
||||
QString title() const;
|
||||
|
||||
QPixmap icon() const;
|
||||
|
||||
int pressGrabTime() const;
|
||||
void setPressGrabTime(int);
|
||||
|
||||
qreal preferredWidth() const;
|
||||
void setPreferredWidth(qreal);
|
||||
qreal preferredHeight() const;
|
||||
void setPreferredHeight(qreal);
|
||||
|
||||
enum Status { Null, Ready, Loading, Error };
|
||||
Status status() const;
|
||||
qreal progress() const;
|
||||
QString statusText() const;
|
||||
|
||||
#ifndef QT_NO_ACTION
|
||||
QAction *reloadAction() const;
|
||||
QAction *backAction() const;
|
||||
QAction *forwardAction() const;
|
||||
QAction *stopAction() const;
|
||||
QAction* action(AmneziaWebView::WebAction) const;
|
||||
#endif
|
||||
|
||||
void load(const QNetworkRequest &request, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
|
||||
const QByteArray &body = QByteArray());
|
||||
|
||||
QString html() const;
|
||||
|
||||
void setHtml(const QString &html, const QUrl &baseUrl = QUrl());
|
||||
void setContent(const QByteArray &data, const QString &mimeType = QString(), const QUrl &baseUrl = QUrl());
|
||||
|
||||
AmneziaWebHistory* history() const;
|
||||
|
||||
QQmlListProperty<QObject> javaScriptWindowObjects();
|
||||
AmneziaWebViewSettings* settingsObject() const;
|
||||
static AmneziaWebViewAttached* qmlAttachedProperties(QObject*);
|
||||
|
||||
QQmlComponent *newWindowComponent() const;
|
||||
void setNewWindowComponent(QQmlComponent *newWindow);
|
||||
QQuickItem* newWindowParent() const;
|
||||
void setNewWindowParent(QQuickItem* newWindow);
|
||||
|
||||
bool isComponentCompletePublic() const { return isComponentComplete(); }
|
||||
|
||||
QSize contentsSize() const;
|
||||
|
||||
void setContentsScale(qreal scale);
|
||||
qreal contentsScale() const;
|
||||
|
||||
QColor backgroundColor() const;
|
||||
void setBackgroundColor(const QColor&);
|
||||
|
||||
void paint(QPainter *painter) override;
|
||||
|
||||
void setDefaultFontSize(int size);
|
||||
void setStandardFontFamily(const QString &family);
|
||||
Q_INVOKABLE void setTextZoom(int percent);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
void preferredWidthChanged();
|
||||
void preferredHeightChanged();
|
||||
|
||||
void urlChanged();
|
||||
void progressChanged();
|
||||
void statusChanged(Status);
|
||||
void titleChanged(const QString&);
|
||||
void iconChanged();
|
||||
void statusTextChanged();
|
||||
void htmlChanged();
|
||||
void pressGrabTimeChanged();
|
||||
void newWindowComponentChanged();
|
||||
void newWindowParentChanged();
|
||||
void renderingEnabledChanged();
|
||||
void contentsSizeChanged(const QSize&);
|
||||
void contentsScaleChanged();
|
||||
void backgroundColorChanged();
|
||||
|
||||
void loadStarted();
|
||||
void loadFinished();
|
||||
void loadFinished(bool ok);
|
||||
void loadFailed();
|
||||
|
||||
void doubleClick(int clickX, int clickY);
|
||||
void zoomTo(qreal zoom, int centerX, int centerY);
|
||||
void alert(const QString& message);
|
||||
|
||||
public Q_SLOTS:
|
||||
void evaluateJavaScript(const QString&);
|
||||
|
||||
private Q_SLOTS:
|
||||
void afterRendering();
|
||||
void updateGeometry();
|
||||
void windowWasChanged(QQuickWindow* window);
|
||||
void parentWasChanged();
|
||||
void fillColorWasChanged();
|
||||
|
||||
void doLoadStarted();
|
||||
void doLoadProgress(int p);
|
||||
void doLoadFinished(bool ok);
|
||||
void setStatusText(const QString&);
|
||||
void windowObjectCleared();
|
||||
|
||||
protected:
|
||||
|
||||
void itemChange(ItemChange, const ItemChangeData &) override;
|
||||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
QScopedPointer<AmneziaWebViewPrivate> d_ptr;
|
||||
|
||||
private:
|
||||
void updateContentsSize() {}
|
||||
void init();
|
||||
void componentComplete() override;
|
||||
QTimer upadeTimer;
|
||||
|
||||
|
||||
Q_DISABLE_COPY(AmneziaWebView)
|
||||
Q_DECLARE_PRIVATE(AmneziaWebView)
|
||||
|
||||
friend class QDeclarativeWebPage;
|
||||
};
|
||||
|
||||
class AmneziaWebViewAttached : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString windowObjectName READ windowObjectName WRITE setWindowObjectName)
|
||||
public:
|
||||
explicit AmneziaWebViewAttached(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString windowObjectName() const
|
||||
{
|
||||
return m_windowObjectName;
|
||||
}
|
||||
|
||||
void setWindowObjectName(const QString &n)
|
||||
{
|
||||
m_windowObjectName = n;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_windowObjectName;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
QML_DECLARE_TYPE(AmneziaWebView)
|
||||
QML_DECLARE_TYPEINFO(AmneziaWebView, QML_HAS_ATTACHED_PROPERTIES)
|
||||
|
||||
#endif
|
||||
369
client/core/webview/amneziawebview_android.cpp
Normal file
369
client/core/webview/amneziawebview_android.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore>
|
||||
#include <QGuiApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QTimer>
|
||||
#include <jni.h>
|
||||
#include <android/bitmap.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "amneziawebview_p.h"
|
||||
#include "qrchandler.h"
|
||||
#include "filehandler.h"
|
||||
|
||||
#include <QJniObject>
|
||||
#include <QJniEnvironment>
|
||||
|
||||
namespace Jni
|
||||
{
|
||||
|
||||
using Object = QJniObject;
|
||||
}
|
||||
static const char qtAndroidWebViewControllerClass[] = "org/amnezia/vpn/WebViewController";
|
||||
|
||||
class AndroidWebViewPrivate;
|
||||
|
||||
typedef QMap<quintptr, AndroidWebViewPrivate *> WebViews;
|
||||
Q_GLOBAL_STATIC(WebViews, g_webViews)
|
||||
Q_GLOBAL_STATIC(QMutex, g_webMutex)
|
||||
|
||||
class AndroidWebViewPrivate : public AmneziaWebViewPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||
|
||||
public:
|
||||
|
||||
explicit AndroidWebViewPrivate(AmneziaWebView* q);
|
||||
virtual ~AndroidWebViewPrivate();
|
||||
|
||||
static AndroidWebViewPrivate *get(AmneziaWebView *q)
|
||||
{
|
||||
return static_cast<AndroidWebViewPrivate*>(AmneziaWebViewPrivate::get(q));
|
||||
}
|
||||
|
||||
virtual void setWindowParent(QWindow *parent);
|
||||
virtual void setBackgroundColor(const QColor backgroundColor);
|
||||
virtual void show();
|
||||
virtual void hide();
|
||||
virtual void setGeometry(const QRect &);
|
||||
virtual QString innerHTML() const;
|
||||
virtual void load(const QUrl& url);
|
||||
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl());
|
||||
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl);
|
||||
virtual void evaluateJavaScript(const QString& scriptSource);
|
||||
virtual bool isLoading() const;
|
||||
virtual bool canGoBack() const;
|
||||
virtual bool canGoForward() const;
|
||||
virtual void back();
|
||||
virtual void forward();
|
||||
virtual void reload();
|
||||
virtual void stop();
|
||||
virtual void setUrl(const QUrl &url);
|
||||
virtual QIcon icon() const;
|
||||
virtual void setScale(qreal scale);
|
||||
virtual qreal scale() const;
|
||||
virtual QSize contentsSize() const;
|
||||
virtual void setDefaultFontSize(int size);
|
||||
virtual void setStandardFontFamily(const QString &family);
|
||||
virtual void setTextZoom(int percent);
|
||||
private:
|
||||
|
||||
quintptr viewId;
|
||||
Jni::Object m_viewController;
|
||||
};
|
||||
|
||||
AndroidWebViewPrivate::AndroidWebViewPrivate(AmneziaWebView* q): AmneziaWebViewPrivate(q),
|
||||
viewId(reinterpret_cast<quintptr>(this))
|
||||
{
|
||||
m_viewController = Jni::Object(qtAndroidWebViewControllerClass,
|
||||
"(Landroid/app/Activity;J)V",
|
||||
QNativeInterface::QAndroidApplication::context().object(),
|
||||
viewId);
|
||||
QMutexLocker lock(g_webMutex());
|
||||
g_webViews->insert(viewId, this);
|
||||
setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
AndroidWebViewPrivate::~AndroidWebViewPrivate()
|
||||
{
|
||||
QMutexLocker lock(g_webMutex());
|
||||
m_viewController.callMethod<void>("release", "()V");
|
||||
g_webViews->take(viewId);
|
||||
}
|
||||
|
||||
AmneziaWebViewPrivate *AmneziaWebViewPrivate::create(AmneziaWebView *q)
|
||||
{
|
||||
return new AndroidWebViewPrivate(q);
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setWindowParent(QWindow *parent)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
}
|
||||
|
||||
|
||||
void AndroidWebViewPrivate::setBackgroundColor(const QColor backgroundColor)
|
||||
{
|
||||
m_viewController.callMethod<void>("setBackgroundColor", "(I)V",
|
||||
jint(backgroundColor.rgb()));
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
|
||||
bool AndroidWebViewPrivate::isLoading() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
void AndroidWebViewPrivate::setScale(qreal scale)
|
||||
{
|
||||
Q_UNUSED(scale);
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
qreal AndroidWebViewPrivate::scale() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QIcon AndroidWebViewPrivate::icon() const
|
||||
{
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QSize AndroidWebViewPrivate::contentsSize() const
|
||||
{
|
||||
return QSize();
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setGeometry(const QRect &geometry)
|
||||
{
|
||||
if (this->geometry != geometry) {
|
||||
this->geometry = geometry;
|
||||
|
||||
m_viewController.callMethod<void>("setGeometry", "(IIII)V",
|
||||
jint(geometry.x()), jint(geometry.y()),
|
||||
jint(geometry.width()), jint(geometry.height()) );
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setTextZoom(int percent)
|
||||
{
|
||||
m_viewController.callMethod<void>("setTextZoom", "(I)V", jint(percent));
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::hide()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (visible) {
|
||||
m_viewController.callMethod<void>("hide", "()V");
|
||||
visible = false;
|
||||
q->update();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::show()
|
||||
{
|
||||
if (!visible) {
|
||||
m_viewController.callMethod<void>("show", "()V");
|
||||
visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AndroidWebViewPrivate::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
Q_UNUSED(mimeType);
|
||||
Q_UNUSED(baseUrl);
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setUrl(const QUrl &url)
|
||||
{
|
||||
AmneziaWebViewPrivate::setUrl(url);
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::load(const QUrl &url)
|
||||
{
|
||||
// Make WebView visible before loading
|
||||
if (!visible) {
|
||||
show();
|
||||
}
|
||||
Jni::Object jurl = Jni::Object::fromString(url.isValid() ? url.toString() : QString("about:blank"));
|
||||
m_viewController.callMethod<void>("loadUrl", "(Ljava/lang/String;)V", jurl.object<jstring>());
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setHtml(const QString &html, const QUrl &baseUrl)
|
||||
{
|
||||
if (html.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Jni::Object url = Jni::Object::fromString(baseUrl.isValid() ? baseUrl.toString() : QString("about:blank"));
|
||||
Jni::Object data = Jni::Object::fromString(html);
|
||||
Jni::Object mime = Jni::Object::fromString(QString("text/html"));
|
||||
Jni::Object encoding = Jni::Object::fromString(QString("utf-8"));
|
||||
|
||||
m_viewController.callMethod<void>("loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
|
||||
url.object<jstring>(), data.object<jstring>(), mime.object<jstring>(), encoding.object<jstring>());
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::evaluateJavaScript(const QString &scriptSource)
|
||||
{
|
||||
Jni::Object script = Jni::Object::fromString(scriptSource);
|
||||
m_viewController.callMethod<void>("evaluateJavaScript", "(Ljava/lang/String;)V", script.object<jstring>());
|
||||
}
|
||||
|
||||
bool AndroidWebViewPrivate::canGoBack() const
|
||||
{
|
||||
jboolean can = m_viewController.callMethod<jboolean>("canGoBack", "()Z");
|
||||
return can;
|
||||
}
|
||||
|
||||
bool AndroidWebViewPrivate::canGoForward() const
|
||||
{
|
||||
jboolean can = m_viewController.callMethod<jboolean>("canGoForward", "()Z");
|
||||
return can;
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::back()
|
||||
{
|
||||
m_viewController.callMethod<void>("goBack", "()V");
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::forward()
|
||||
{
|
||||
m_viewController.callMethod<void>("goForward", "()V");
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::reload()
|
||||
{
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::stop()
|
||||
{
|
||||
}
|
||||
|
||||
QString AndroidWebViewPrivate::innerHTML() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setDefaultFontSize(int size)
|
||||
{
|
||||
m_viewController.callMethod<void>("setDefaultFontSize", "(I)V", jint(size));
|
||||
}
|
||||
|
||||
void AndroidWebViewPrivate::setStandardFontFamily(const QString &family)
|
||||
{
|
||||
Jni::Object fontFamily = Jni::Object::fromString(family);
|
||||
m_viewController.callMethod<void>("setStandardFontFamily", "(Ljava/lang/String;)V", fontFamily.object<jstring>());
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void Java_org_amnezia_vpn_WebViewController_pageStarted(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(obj);
|
||||
|
||||
QMutexLocker lock(g_webMutex());
|
||||
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||
if (view) {
|
||||
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||
const QUrl url = QUrl(QString(urlChars));
|
||||
QMetaObject::invokeMethod(view, "onPageStarted", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_amnezia_vpn_WebViewController_pageFinished(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(obj);
|
||||
QMutexLocker lock(g_webMutex());
|
||||
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||
if (view) {
|
||||
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||
const QUrl url = QUrl(QString(urlChars));
|
||||
QMetaObject::invokeMethod(view, "onPageFinished", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_org_amnezia_vpn_WebViewController_urlChanged(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(obj);
|
||||
QMutexLocker lock(g_webMutex());
|
||||
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||
if (view) {
|
||||
|
||||
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||
const QUrl url = QUrl(QString(urlChars));
|
||||
QMetaObject::invokeMethod(view, "onUrlChanged", Qt::QueuedConnection, Q_ARG( const QUrl, url));
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jbyteArray Java_org_amnezia_vpn_WebViewController_dataForUrl(JNIEnv *env, jobject obj, jlong viewId, jstring url, jobject mimeType, jobject encoding)
|
||||
{
|
||||
Q_UNUSED(env)
|
||||
Q_UNUSED(obj)
|
||||
|
||||
QMutexLocker lock(g_webMutex());
|
||||
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||
if (view) {
|
||||
|
||||
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||
const QUrl url = QUrl(QString(urlChars));
|
||||
|
||||
QByteArray buffer = view->dataForUrl(url);
|
||||
QString mime = view->mimeTypeForUrl(url);
|
||||
QString enc("utf-8");
|
||||
|
||||
jstring jMimeType = env->NewStringUTF(mime.toUtf8().constData());
|
||||
Jni::Object jMimeTypeObject(mimeType);
|
||||
if (jMimeTypeObject.isValid()) {
|
||||
jMimeTypeObject.callObjectMethod("insert", "(ILjava/lang/String;)Ljava/lang/StringBuilder;", 0, jMimeType);
|
||||
}
|
||||
|
||||
jstring jEncoding = env->NewStringUTF(enc.toUtf8().constData());
|
||||
Jni::Object jEncodingObject(encoding);
|
||||
if (jEncodingObject.isValid()) {
|
||||
|
||||
jEncodingObject.callObjectMethod("insert", "(ILjava/lang/String;)Ljava/lang/StringBuilder;", 0, jEncoding);
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(jEncoding);
|
||||
env->DeleteLocalRef(jMimeType);
|
||||
|
||||
jbyteArray data = env->NewByteArray(buffer.size());
|
||||
env->SetByteArrayRegion(data, 0, buffer.size(), (const jbyte*) buffer.data());
|
||||
return data;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean Java_org_amnezia_vpn_WebViewController_canHandleUrl(JNIEnv *env, jobject obj, jlong viewId, jstring url)
|
||||
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(obj);
|
||||
QMutexLocker lock(g_webMutex());
|
||||
AndroidWebViewPrivate *view = g_webViews()->value(viewId);
|
||||
if (view) {
|
||||
const char *urlChars = env->GetStringUTFChars(url, 0);
|
||||
const QUrl url = QUrl(QString(urlChars));
|
||||
return view->canHandleUrl(url);
|
||||
}
|
||||
|
||||
return jboolean(false);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
433
client/core/webview/amneziawebview_desktop.cpp
Normal file
433
client/core/webview/amneziawebview_desktop.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
#include <QtCore>
|
||||
#include <QDebug>
|
||||
#include <QBoxLayout>
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QStyle>
|
||||
#include <QQuickWindow>
|
||||
|
||||
#include "amneziawebview_desktop_p.h"
|
||||
#include "qrchandler.h"
|
||||
#include "filehandler.h"
|
||||
|
||||
typedef QMap<quintptr, AmneziaWebViewPrivate *> WebViews;
|
||||
Q_GLOBAL_STATIC(WebViews, g_webViews)
|
||||
|
||||
|
||||
QrcHandler::QrcHandler()
|
||||
{}
|
||||
|
||||
|
||||
FileHandler::FileHandler()
|
||||
{
|
||||
}
|
||||
|
||||
JsHandler::JsHandler(AmneziaWebView *host): _host(host), scriptObjectsInjected(false)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
JsHandler::~JsHandler() {}
|
||||
|
||||
WebPage::WebPage(QObject *parent)
|
||||
: QWebPage(parent)
|
||||
{
|
||||
connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
|
||||
this, SLOT(handleUnsupportedContent(QNetworkReply*)));
|
||||
}
|
||||
|
||||
void WebPage::javaScriptAlert(QWebFrame *frame, const QString& msg)
|
||||
{
|
||||
Q_UNUSED(frame)
|
||||
Q_UNUSED(msg)
|
||||
}
|
||||
|
||||
WebPage::~WebPage()
|
||||
{
|
||||
disconnect(this);
|
||||
}
|
||||
|
||||
bool WebPage::acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type)
|
||||
{
|
||||
return QWebPage::acceptNavigationRequest(frame, request, type);
|
||||
}
|
||||
|
||||
void WebPage::handleUnsupportedContent(QNetworkReply *reply)
|
||||
{
|
||||
QString errorString = reply->errorString();
|
||||
|
||||
if (m_loadingUrl != reply->url()) {
|
||||
// sub resource of this page
|
||||
qWarning() << "Resource" << reply->url().toEncoded() << "has unknown Content-Type, will be ignored.";
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && !reply->header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||
errorString = "Unknown Content-Type";
|
||||
}
|
||||
|
||||
QFile file(QLatin1String(":/notfound.html"));
|
||||
bool isOpened = file.open(QIODevice::ReadOnly);
|
||||
Q_ASSERT(isOpened);
|
||||
Q_UNUSED(isOpened)
|
||||
|
||||
QString title = QCoreApplication::translate("webview", "Error loading page: %1").arg(reply->url().toString());
|
||||
QString html = QString(QLatin1String(file.readAll()))
|
||||
.arg(title)
|
||||
.arg(errorString)
|
||||
.arg(reply->url().toString());
|
||||
|
||||
QBuffer imageBuffer;
|
||||
imageBuffer.open(QBuffer::ReadWrite);
|
||||
QIcon icon = view()->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, view());
|
||||
QPixmap pixmap = icon.pixmap(QSize(32,32));
|
||||
if (pixmap.save(&imageBuffer, "PNG")) {
|
||||
html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"),
|
||||
QString(QLatin1String(imageBuffer.buffer().toBase64())));
|
||||
}
|
||||
|
||||
QList<QWebFrame*> frames;
|
||||
frames.append(mainFrame());
|
||||
while (!frames.isEmpty()) {
|
||||
QWebFrame *frame = frames.takeFirst();
|
||||
if (frame->url() == reply->url()) {
|
||||
frame->setHtml(html, reply->url());
|
||||
return;
|
||||
}
|
||||
QList<QWebFrame *> children = frame->childFrames();
|
||||
foreach(QWebFrame *frame, children)
|
||||
frames.append(frame);
|
||||
}
|
||||
if (m_loadingUrl == reply->url()) {
|
||||
mainFrame()->setHtml(html, reply->url());
|
||||
}
|
||||
}
|
||||
|
||||
DesktopWebViewPrivate::DesktopWebViewPrivate(AmneziaWebView* q): AmneziaWebViewPrivate(q)
|
||||
, viewId(reinterpret_cast<quintptr>(this))
|
||||
, containerWindow(nullptr)
|
||||
, window(nullptr)
|
||||
{
|
||||
container = new QWidget(0, Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Tool);
|
||||
container->setAttribute(Qt::WA_NativeWindow, true);
|
||||
container->setAttribute(Qt::WA_DontCreateNativeAncestors, true);
|
||||
|
||||
// Do not remove next line -> prevent some sort of spontaneous crashes
|
||||
QWebSettings::setObjectCacheCapacities(0, 0, 0);
|
||||
|
||||
view = new QWebView(container);
|
||||
WebPage *page = new WebPage(view);
|
||||
page->setForwardUnsupportedContent(true);
|
||||
page->setNetworkAccessManager(networkAccessManager());
|
||||
page->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
|
||||
view->setPage(page);
|
||||
|
||||
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
container->setLayout(new QHBoxLayout(container));
|
||||
container->layout()->setSpacing(0);
|
||||
container->layout()->setMargin(0);
|
||||
container->layout()->addWidget(view);
|
||||
|
||||
setBackgroundColor(backgroundColor);
|
||||
g_webViews->insert(viewId, this);
|
||||
|
||||
connect(view, SIGNAL(loadFinished(bool)), this, SIGNAL(loadFinished(bool)));
|
||||
|
||||
connect(page, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinished(bool)), Qt::QueuedConnection);
|
||||
connect(page, SIGNAL(loadStarted()), this, SLOT(onPageStarted()), Qt::QueuedConnection);
|
||||
connect(view, SIGNAL(urlChanged(const QUrl &)), this, SLOT(onUrlChanged(const QUrl &)), Qt::QueuedConnection);
|
||||
container->createWinId();
|
||||
}
|
||||
|
||||
DesktopWebViewPrivate::~DesktopWebViewPrivate()
|
||||
{
|
||||
disconnect(this, SLOT(loadFinished(bool)));
|
||||
disconnect(this, SLOT(applicationStateChanged(Qt::ApplicationState)));
|
||||
disconnect(this, SLOT(onUrlChanged(const QUrl &)));
|
||||
disconnect(this, SLOT(onPageStarted()));
|
||||
disconnect(this, SLOT(onLoadFinished(bool)));
|
||||
|
||||
g_webViews->take(viewId);
|
||||
view->stop();
|
||||
view->setPage(nullptr);
|
||||
|
||||
container->deleteLater();
|
||||
}
|
||||
|
||||
AmneziaWebViewPrivate *AmneziaWebViewPrivate::create(AmneziaWebView *q)
|
||||
{
|
||||
return new DesktopWebViewPrivate(q);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setWindowParent(QWindow *parent)
|
||||
{
|
||||
if (window) {
|
||||
window->removeEventFilter(this);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
|
||||
containerWindow = qobject_cast<QWindow*>(container->windowHandle());
|
||||
containerWindow->setTransientParent(parent);
|
||||
parent->installEventFilter(this);
|
||||
|
||||
}
|
||||
window = parent;
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setBackgroundColor(const QColor backgroundColor)
|
||||
{
|
||||
this->backgroundColor = backgroundColor;
|
||||
QPalette p = container->palette();
|
||||
p.setColor(QPalette::Background, backgroundColor);
|
||||
container->setPalette(p);
|
||||
p = view->palette();
|
||||
p.setColor(QPalette::Background, backgroundColor);
|
||||
view->setPalette(p);
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
void DesktopWebViewPrivate::setScale(qreal scale)
|
||||
{
|
||||
Q_UNUSED(scale)
|
||||
//qreal s = view->geometry().width() / view->page()->preferredContentsSize().width();
|
||||
//view->setZoomFactor(s);
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
qreal DesktopWebViewPrivate::scale() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QIcon DesktopWebViewPrivate::icon() const
|
||||
{
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QSize DesktopWebViewPrivate::contentsSize() const
|
||||
{
|
||||
return QSize();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::takeSnapshot()
|
||||
{
|
||||
if (geometry.isEmpty() || !containerWindow) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
||||
container->updateGeometry();
|
||||
QPixmap pixmap = container->grab();
|
||||
snapshot = pixmap.toImage();
|
||||
emit snapshotChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setGeometry(const QRect &geometry)
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
QQuickWindow *window = q->window();
|
||||
if (!window) return;
|
||||
QRect newGeometry = QRect(window->mapToGlobal(geometry.topLeft()), QSize(geometry.width(), geometry.height()));
|
||||
if (newGeometry.isValid() && container->geometry() != newGeometry ) {
|
||||
|
||||
this->geometry = geometry;
|
||||
container->setGeometry(newGeometry);
|
||||
container->updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
Q_UNUSED(obj)
|
||||
Q_Q(AmneziaWebView);
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::Move: {
|
||||
QMoveEvent *moveEvent = static_cast<QMoveEvent*>(event);
|
||||
QPoint p = q->mapToScene(QPointF(moveEvent->pos())).toPoint();
|
||||
container->move(p);
|
||||
//container->updateGeometry();
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case QEvent::Resize: {
|
||||
|
||||
//QRect newGeometry = QRect(window->mapToGlobal(geometry.topLeft()), QSize(geometry.width(), geometry.height()));
|
||||
//container->setGeometry(newGeometry);
|
||||
//container->updateGeometry();
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case QEvent::WindowStateChange: {
|
||||
|
||||
Qt::WindowState state = window->windowState();
|
||||
if ((state == Qt::WindowMaximized) || (state == Qt::WindowFullScreen) || (state == Qt::WindowActive)) {
|
||||
show();
|
||||
}
|
||||
else {
|
||||
hide();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::hide()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (!q->window()) return;
|
||||
|
||||
if (visible) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "requestSnapshot", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(container, "hide", Qt::QueuedConnection);
|
||||
visible = false;
|
||||
|
||||
//if (q->isVisible())
|
||||
// q->update();
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::show()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (!q->window()) return;
|
||||
if (!visible) {
|
||||
|
||||
QMetaObject::invokeMethod(container, "show", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(container, "update", Qt::QueuedConnection);
|
||||
visible = true;
|
||||
}
|
||||
|
||||
if ((qApp->topLevelWindows().at(0) != containerWindow) || !containerWindow->isVisible()) {
|
||||
|
||||
containerWindow->raise();
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::load(const QUrl& baseUrl)
|
||||
{
|
||||
QUrl url = baseUrl;
|
||||
if (!url.isValid()) {
|
||||
url = QUrl(QLatin1String("about:blank"));
|
||||
}
|
||||
view->load(url);
|
||||
history()->append(baseUrl);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||
{
|
||||
QUrl url = baseUrl;
|
||||
if (!url.isValid()) {
|
||||
url = QUrl(QLatin1String("about:blank"));
|
||||
}
|
||||
view->setContent(data, mimeType, url);
|
||||
history()->append(url, data, mimeType);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setHtml(const QString& html, const QUrl& baseUrl)
|
||||
{
|
||||
if(html.isNull()) return;
|
||||
QUrl url = baseUrl;
|
||||
if (!baseUrl.isValid())
|
||||
url = QUrl(QLatin1String("about:blank"));
|
||||
|
||||
view->setHtml(html, url);
|
||||
history()->append(url, html.toUtf8(), "text/html");
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::evaluateJavaScript(const QString& scriptSource)
|
||||
{
|
||||
view->page()->mainFrame()->evaluateJavaScript(scriptSource);
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::canGoBack() const
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebPage::Back);
|
||||
bool can = (pageAction && pageAction->isEnabled());
|
||||
return can;
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::canGoForward() const
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebPage::Forward);
|
||||
bool can = (pageAction && pageAction->isEnabled());
|
||||
return can;
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::back()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebPage::Back);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::forward()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebPage::Forward);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::reload()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebPage::Reload);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::stop()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebPage::Stop);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::isLoading() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DesktopWebViewPrivate::innerHTML() const
|
||||
{
|
||||
QVariant result = view->page()->mainFrame()->evaluateJavaScript("document.body.innerHTML");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::onLoadFinished(bool success)
|
||||
{
|
||||
if (success) {
|
||||
QMetaObject::invokeMethod(this, "onPageFinished", Qt::QueuedConnection);
|
||||
}
|
||||
else {
|
||||
QMetaObject::invokeMethod(this, "onPageError", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setDefaultFontSize(int size)
|
||||
{
|
||||
view->settings()->setFontSize(QWebSettings::DefaultFontSize, size);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setStandardFontFamily(const QString &family)
|
||||
{
|
||||
view->settings()->setFontFamily(QWebSettings::StandardFont, family);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setTextZoom(int percent)
|
||||
{
|
||||
Q_UNUSED(percent)
|
||||
}
|
||||
96
client/core/webview/amneziawebview_desktop_p.h
Normal file
96
client/core/webview/amneziawebview_desktop_p.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef AMNEZIAWEBVIEW_DESKTOP_P_H
|
||||
#define AMNEZIAWEBVIEW_DESKTOP_P_H
|
||||
|
||||
// QtWebKit is deprecated and not available in Qt 6
|
||||
// This file should only be used with Qt 5 when WebEngineWidgets is not available
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#error "amneziawebview_desktop_p.h uses QtWebKit which is not available in Qt 6. Use amneziawebview_webengine_p.h instead."
|
||||
#endif
|
||||
|
||||
#include <QtWebKitWidgets/QWebView>
|
||||
#include <QtWebKitWidgets/QWebPage>
|
||||
#include <QtWebKitWidgets/QWebFrame>
|
||||
#include <QtWebKit/QWebSettings>
|
||||
|
||||
#include "amneziawebview.h"
|
||||
#include "amneziawebview_p.h"
|
||||
|
||||
class DesktopWebViewPrivate;
|
||||
|
||||
class WebPage : public QWebPage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void loadingUrl(const QUrl &url);
|
||||
|
||||
public:
|
||||
explicit WebPage(QObject *parent = nullptr);
|
||||
virtual ~WebPage();
|
||||
|
||||
protected:
|
||||
bool acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type);
|
||||
virtual void javaScriptAlert(QWebFrame *frame, const QString& msg);
|
||||
|
||||
private slots:
|
||||
void handleUnsupportedContent(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
|
||||
friend class DesktopWebViewPrivate;
|
||||
QUrl m_loadingUrl;
|
||||
};
|
||||
|
||||
class DesktopWebViewPrivate : public AmneziaWebViewPrivate
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||
public:
|
||||
|
||||
explicit DesktopWebViewPrivate(AmneziaWebView* q);
|
||||
virtual ~DesktopWebViewPrivate();
|
||||
|
||||
virtual void setWindowParent(QWindow *parent);
|
||||
|
||||
virtual void setBackgroundColor(const QColor backgroundColor);
|
||||
virtual void show();
|
||||
virtual void hide();
|
||||
virtual void takeSnapshot();
|
||||
virtual void setGeometry(const QRect &);
|
||||
virtual QString innerHTML() const;
|
||||
virtual void load(const QUrl& url);
|
||||
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl());
|
||||
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl);
|
||||
virtual void evaluateJavaScript(const QString& scriptSource);
|
||||
virtual bool isLoading() const;
|
||||
virtual bool canGoBack() const;
|
||||
virtual bool canGoForward() const;
|
||||
virtual void back();
|
||||
virtual void forward();
|
||||
virtual void reload();
|
||||
virtual void stop();
|
||||
|
||||
virtual QIcon icon() const;
|
||||
virtual void setScale(qreal scale);
|
||||
virtual qreal scale() const;
|
||||
virtual QSize contentsSize() const;
|
||||
virtual void setDefaultFontSize(int size);
|
||||
virtual void setTextZoom(int percent) { Q_UNUSED(percent); }
|
||||
virtual void setStandardFontFamily(const QString &family);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
private slots:
|
||||
void onLoadFinished(bool);
|
||||
|
||||
private:
|
||||
quintptr viewId;
|
||||
QWebView *view;
|
||||
QWidget *container;
|
||||
QWindow *containerWindow;
|
||||
QWindow *window;
|
||||
};
|
||||
|
||||
|
||||
#endif // AMNEZIAWEBVIEW_DESKTOP_P_H
|
||||
1045
client/core/webview/amneziawebview_ios.mm
Normal file
1045
client/core/webview/amneziawebview_ios.mm
Normal file
File diff suppressed because it is too large
Load Diff
423
client/core/webview/amneziawebview_p.cpp
Normal file
423
client/core/webview/amneziawebview_p.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
#include "amneziawebview_p.h"
|
||||
#include "amneziawebhistory.h"
|
||||
#include "websettings.h"
|
||||
|
||||
NetworkAccessManager::NetworkAccessManager(QNetworkAccessManager *manager, QObject *parent)
|
||||
: QNetworkAccessManager(parent)
|
||||
{
|
||||
this->manager = manager;
|
||||
setCache(manager->cache());
|
||||
setCookieJar(manager->cookieJar());
|
||||
setProxy(manager->proxy());
|
||||
setProxyFactory(manager->proxyFactory());
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
void NetworkAccessManager::init()
|
||||
{
|
||||
connect(this, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this,
|
||||
SLOT(handleSslErrors(QNetworkReply*, const QList<QSslError> & )));
|
||||
}
|
||||
|
||||
QNetworkReply *NetworkAccessManager::createRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, QIODevice *device)
|
||||
{
|
||||
AmneziaWebView *view = qobject_cast<AmneziaWebView *>(parent());
|
||||
AmneziaWebViewPrivate *d = AmneziaWebViewPrivate::get(view);
|
||||
|
||||
if (!d || !d->canHandleUrl(request.url())) {
|
||||
return QNetworkAccessManager::createRequest(operation, request, device);
|
||||
}
|
||||
|
||||
if (operation == GetOperation) {
|
||||
return new DataReply(this, operation, request, view);
|
||||
}
|
||||
else
|
||||
|
||||
return QNetworkAccessManager::createRequest(operation, request, device);
|
||||
}
|
||||
|
||||
void NetworkAccessManager::handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors)
|
||||
{
|
||||
Q_UNUSED(errors)
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
|
||||
DataReply::DataReply(QObject *parent, const QNetworkAccessManager::Operation operation, const QNetworkRequest &request, AmneziaWebView *view): QNetworkReply(parent)
|
||||
{
|
||||
setRequest(request);
|
||||
setUrl(request.url());
|
||||
setOperation(operation);
|
||||
setFinished(true);
|
||||
this->view = view;
|
||||
offset = 0;
|
||||
|
||||
//QUrl url = request.url();
|
||||
//url.setHost(QString());
|
||||
//if (url.path().isEmpty())
|
||||
// url.setPath(QLatin1String("/"));
|
||||
//setUrl(url);
|
||||
|
||||
QMetaObject::invokeMethod(this, "setContent", Qt::QueuedConnection );
|
||||
}
|
||||
|
||||
void DataReply::setContent()
|
||||
{
|
||||
if (!view || view.isNull()) return;
|
||||
|
||||
AmneziaWebViewPrivate *q = AmneziaWebViewPrivate::get(view);
|
||||
content = q->dataForUrl(url());
|
||||
QString mimeType = q->mimeTypeForUrl(url()).toLower();
|
||||
//open(ReadOnly | Unbuffered);
|
||||
QNetworkReply::open(QIODevice::ReadOnly);
|
||||
|
||||
int size = content.size();
|
||||
if (size <= 0 ) {
|
||||
|
||||
QString msg = QString("Error opening %1").arg(url().toString());
|
||||
qCritical() << msg;
|
||||
setError(QNetworkReply::ContentNotFoundError, msg);
|
||||
QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
|
||||
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ContentNotFoundError));
|
||||
|
||||
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QString("%1; charset=%2").arg(mimeType, "utf-8")));
|
||||
|
||||
setHeader(QNetworkRequest::ContentLengthHeader, size);
|
||||
QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
|
||||
Q_ARG(qint64, size), Q_ARG(qint64, size));
|
||||
QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void DataReply::abort()
|
||||
{
|
||||
QNetworkReply::close();
|
||||
}
|
||||
|
||||
void DataReply::close()
|
||||
{
|
||||
QNetworkReply::close();
|
||||
}
|
||||
|
||||
qint64 DataReply::size() const
|
||||
{
|
||||
return content.size();
|
||||
}
|
||||
|
||||
bool DataReply::isSequential() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 DataReply::bytesAvailable() const
|
||||
{
|
||||
return content.size() - offset + QIODevice::bytesAvailable();
|
||||
}
|
||||
qint64 DataReply::readData(char *data, qint64 maxSize)
|
||||
{
|
||||
if (offset < content.size()) {
|
||||
qint64 number = qMin(maxSize, content.size() - offset);
|
||||
memcpy(data, content.constData() + offset, number);
|
||||
offset += number;
|
||||
return number;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
AmneziaWebViewPrivate::AmneziaWebViewPrivate(AmneziaWebView *q) : QObject(q)
|
||||
, pending(PendingNone)
|
||||
, status(AmneziaWebView::Null)
|
||||
, preferredwidth(0)
|
||||
, preferredheight(0)
|
||||
, progress(1.0)
|
||||
, newWindowComponent(nullptr)
|
||||
, newWindowParent(nullptr)
|
||||
, jsHandler(q)
|
||||
, rendering(true)
|
||||
, overlapped(false)
|
||||
, backgroundColor(QColor::fromRgb(28, 29, 33))
|
||||
, visible(false)
|
||||
, networkManager(nullptr)
|
||||
, q_ptr(q)
|
||||
, m_history(new AmneziaWebHistory(q))
|
||||
, m_settings(new AmneziaWebViewSettings(q))
|
||||
{
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::init()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
m_settings->apply();
|
||||
|
||||
actions.setMapping(new QAction(q), (int)AmneziaWebView::Back);
|
||||
actions.setMapping(new QAction(q), (int)AmneziaWebView::Forward);
|
||||
actions.setMapping(new QAction(q), (int)AmneziaWebView::Stop);
|
||||
actions.setMapping(new QAction(q), (int)AmneziaWebView::Reload);
|
||||
connect(action(AmneziaWebView::Back), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||
connect(action(AmneziaWebView::Forward), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||
connect(action(AmneziaWebView::Forward), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||
connect(action(AmneziaWebView::Forward), SIGNAL(triggered()), &actions, SLOT(map()));
|
||||
connect(&actions, SIGNAL(mappedInt(int)), this, SLOT(onAction(int)));
|
||||
connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(applicationStateChanged(Qt::ApplicationState)));
|
||||
}
|
||||
|
||||
AmneziaWebViewPrivate::~AmneziaWebViewPrivate()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
disconnect(this, SLOT(onAction(int)));
|
||||
disconnect(&actions, SLOT(map()));
|
||||
if (networkManager && networkManager->parent() == q)
|
||||
delete networkManager;
|
||||
}
|
||||
|
||||
AmneziaWebViewPrivate *AmneziaWebViewPrivate::get(AmneziaWebView *q)
|
||||
{
|
||||
if (!q) { return nullptr; }
|
||||
return q->d_func();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::applicationStateChanged(Qt::ApplicationState state)
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if ((state == Qt::ApplicationActive) && q->isVisible()) {
|
||||
emit q->update();
|
||||
}
|
||||
else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::requestShow()
|
||||
{
|
||||
show();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::requestHide()
|
||||
{
|
||||
hide();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::move(const QPoint &point)
|
||||
{
|
||||
QRect newGeomentry = geometry;
|
||||
newGeomentry.moveTo(point);
|
||||
setGeometry(newGeomentry);
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::windowObjectsClear(QQmlListProperty<QObject>* prop)
|
||||
{
|
||||
static_cast<AmneziaWebViewPrivate*>(prop->data)->windowObjects.clear();
|
||||
}
|
||||
|
||||
QObject *AmneziaWebViewPrivate::windowObjectsAt(QQmlListProperty<QObject> *prop, qsizetype index)
|
||||
{
|
||||
return static_cast<AmneziaWebViewPrivate *>(prop->data)->windowObjects.at(index);
|
||||
}
|
||||
|
||||
qsizetype AmneziaWebViewPrivate::windowObjectsCount(QQmlListProperty<QObject> *prop)
|
||||
{
|
||||
return static_cast<AmneziaWebViewPrivate *>(prop->data)->windowObjects.count();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::windowObjectsAppend(QQmlListProperty<QObject>* prop, QObject* o)
|
||||
{
|
||||
static_cast<AmneziaWebViewPrivate*>(prop->data)->windowObjects.append(o);
|
||||
static_cast<AmneziaWebViewPrivate*>(prop->data)->updateWindowObjects();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::setTitle(const QString &title)
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (this->title != title) {
|
||||
this->title = title;
|
||||
emit q->titleChanged(this->title);
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray AmneziaWebViewPrivate::dataForUrl(const QUrl &url) const
|
||||
{
|
||||
QByteArray data;
|
||||
if (qrcHandler.canHandleUrl(url)) {
|
||||
data = qrcHandler.dataForUrl(url);
|
||||
}
|
||||
else if (jsHandler.canHandleUrl(url)) {
|
||||
|
||||
data = jsHandler.dataForUrl(url);
|
||||
}
|
||||
else if (fileHandler.canHandleUrl(url)) {
|
||||
|
||||
data = fileHandler.dataForUrl(url);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
QString AmneziaWebViewPrivate::mimeTypeForUrl(const QUrl &url) const
|
||||
{
|
||||
QString mimeType("application/octet-stream");
|
||||
if (qrcHandler.canHandleUrl(url)) {
|
||||
mimeType = qrcHandler.mimeTypeForUrl(url);
|
||||
}
|
||||
else if (jsHandler.canHandleUrl(url)) {
|
||||
mimeType = jsHandler.mimeTypeForUrl(url);
|
||||
}
|
||||
else if (fileHandler.canHandleUrl(url)) {
|
||||
mimeType = fileHandler.mimeTypeForUrl(url);
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::onPageStarted()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
emit q->loadStarted();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::onPageFinished()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
jsHandler.updateWebView();
|
||||
|
||||
if (lastError.length() > 0) {
|
||||
emit q->loadFinished(false);
|
||||
}
|
||||
else {
|
||||
emit q->loadFinished(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::onPageError()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::onUrlChanged(const QUrl &url)
|
||||
{
|
||||
history()->append(url);
|
||||
setUrl(url);
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::setUrl(const QUrl &url)
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
|
||||
QString urlString = url.toString();
|
||||
while ( urlString.endsWith('#')) urlString.chop(1);
|
||||
QUrl newUrl(urlString);
|
||||
|
||||
if (newUrl == QUrl(QLatin1String("about:blank")) ) {
|
||||
newUrl = QUrl("");
|
||||
}
|
||||
|
||||
if (this->url != newUrl) {
|
||||
|
||||
this->url = newUrl;
|
||||
emit q->urlChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::addToJavaScriptWindowObject(const QString& name, QObject* object)
|
||||
{
|
||||
jsHandler.addToJavaScriptWindowObject(name, object);
|
||||
}
|
||||
|
||||
|
||||
bool AmneziaWebViewPrivate::canHandleUrl(const QUrl &url) const
|
||||
{
|
||||
bool can = (jsHandler.canHandleUrl(url) || qrcHandler.canHandleUrl(url) || fileHandler.canHandleUrl(url));
|
||||
return can;
|
||||
}
|
||||
|
||||
QString AmneziaWebViewPrivate::toHtml() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::load(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& body)
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
Q_UNUSED(operation)
|
||||
Q_UNUSED(body)
|
||||
}
|
||||
|
||||
QNetworkAccessManager* AmneziaWebViewPrivate::networkAccessManager()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
|
||||
if (!networkManager)
|
||||
networkManager = new NetworkAccessManager(q);
|
||||
return networkManager;
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::setNetworkAccessManager(QNetworkAccessManager* manager)
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (manager == networkManager)
|
||||
return;
|
||||
if (networkManager && networkManager->parent() == q)
|
||||
delete networkManager;
|
||||
|
||||
NetworkAccessManager *newManager = qobject_cast<NetworkAccessManager *>(manager);
|
||||
if (!newManager && manager) {
|
||||
newManager = new NetworkAccessManager(manager, q);
|
||||
}
|
||||
networkManager = newManager;
|
||||
}
|
||||
|
||||
QAction *AmneziaWebViewPrivate::action(AmneziaWebView::WebAction action) const
|
||||
{
|
||||
QAction *ret = qobject_cast<QAction*>(actions.mapping((int)action));
|
||||
if (ret) {
|
||||
switch (action) {
|
||||
case AmneziaWebView::Back: {
|
||||
bool can = canGoBack() || history()->canGoBack();
|
||||
ret->setEnabled(can);
|
||||
}
|
||||
break;
|
||||
case AmneziaWebView::Forward: {
|
||||
bool can = canGoForward() || history()->canGoForward();
|
||||
ret->setEnabled(can);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmneziaWebViewPrivate::onAction(int action)
|
||||
{
|
||||
switch (action) {
|
||||
case AmneziaWebView::Back: {
|
||||
if (canGoBack()) {
|
||||
back();
|
||||
}
|
||||
//else {
|
||||
// history()->back();
|
||||
//}
|
||||
}
|
||||
break;
|
||||
case AmneziaWebView::Forward:
|
||||
if (canGoForward()) forward();
|
||||
break;
|
||||
case AmneziaWebView::Stop:
|
||||
stop();
|
||||
break;
|
||||
case AmneziaWebView::Reload:
|
||||
reload();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AmneziaWebHistory* AmneziaWebViewPrivate::history() const
|
||||
{
|
||||
return m_history.data();
|
||||
}
|
||||
187
client/core/webview/amneziawebview_p.h
Normal file
187
client/core/webview/amneziawebview_p.h
Normal file
@@ -0,0 +1,187 @@
|
||||
#ifndef WEBVIEW_P_H
|
||||
#define WEBVIEW_P_H
|
||||
|
||||
#include "amneziawebview.h"
|
||||
#include "qrchandler.h"
|
||||
#include "jshandler.h"
|
||||
#include "filehandler.h"
|
||||
|
||||
#include "amneziawebhistory.h"
|
||||
|
||||
class WebSettings;
|
||||
class QIcon;
|
||||
class QSize;
|
||||
|
||||
|
||||
class WebViewSettings;
|
||||
class WebSettings;
|
||||
|
||||
class AmneziaWebViewPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||
|
||||
public:
|
||||
explicit AmneziaWebViewPrivate(AmneziaWebView *q);
|
||||
virtual ~AmneziaWebViewPrivate();
|
||||
static AmneziaWebViewPrivate *create(AmneziaWebView *q);
|
||||
void init();
|
||||
|
||||
virtual void setWindowParent(QWindow *parent) = 0;
|
||||
virtual void setBackgroundColor(const QColor backgroundColor) = 0;
|
||||
|
||||
virtual void setGeometry(const QRect &) = 0;
|
||||
virtual QString innerHTML() const = 0;
|
||||
virtual void load(const QUrl& url) = 0;
|
||||
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl()) = 0;
|
||||
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl) = 0;
|
||||
virtual void evaluateJavaScript(const QString& scriptSource) = 0;
|
||||
virtual bool isLoading() const = 0;
|
||||
virtual bool canGoBack() const = 0;
|
||||
virtual bool canGoForward() const = 0;
|
||||
virtual void back() = 0;
|
||||
virtual void forward() = 0;
|
||||
virtual void reload() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual QIcon icon() const = 0;
|
||||
virtual void setScale(qreal scale) = 0;
|
||||
virtual qreal scale() const = 0;
|
||||
virtual QSize contentsSize() const = 0;
|
||||
virtual void show() = 0;
|
||||
virtual void hide() = 0;
|
||||
virtual void setDefaultFontSize(int size) = 0;
|
||||
virtual void setStandardFontFamily(const QString &family) = 0;
|
||||
virtual void setTextZoom(int percent) = 0;
|
||||
|
||||
virtual void setUrl(const QUrl &url);
|
||||
|
||||
static AmneziaWebViewPrivate *get(AmneziaWebView *q);
|
||||
|
||||
enum { PendingNone, PendingUrl, PendingHtml, PendingContent } pending;
|
||||
|
||||
QString toHtml() const;
|
||||
AmneziaWebHistory* history() const;
|
||||
|
||||
QByteArray dataForUrl(const QUrl &url) const;
|
||||
QString mimeTypeForUrl(const QUrl &url) const;
|
||||
bool canHandleUrl(const QUrl &url) const;
|
||||
|
||||
static void windowObjectsClear(QQmlListProperty<QObject> *prop);
|
||||
static QObject *windowObjectsAt(QQmlListProperty<QObject> *prop, qsizetype index);
|
||||
static qsizetype windowObjectsCount(QQmlListProperty<QObject> *prop);
|
||||
static void windowObjectsAppend(QQmlListProperty<QObject> *prop, QObject *o);
|
||||
|
||||
void updateWindowObjects();
|
||||
QObjectList windowObjects;
|
||||
|
||||
QNetworkAccessManager* networkAccessManager();
|
||||
void setNetworkAccessManager(QNetworkAccessManager* manager);
|
||||
void load(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& body);
|
||||
QAction *action(AmneziaWebView::WebAction) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void setTitle(const QString &title);
|
||||
void move(const QPoint &);
|
||||
void requestHide();
|
||||
void requestShow();
|
||||
|
||||
Q_SIGNALS:
|
||||
void loadStarted();
|
||||
void loadFinished(bool ok);
|
||||
void loadProgress(int progress);
|
||||
void titleChanged(const QString& title);
|
||||
void urlChanged(const QUrl& url);
|
||||
void backgroundColorChanged();
|
||||
public:
|
||||
|
||||
QUrl url;
|
||||
AmneziaWebView::Status status;
|
||||
qreal preferredwidth, preferredheight;
|
||||
qreal progress;
|
||||
QString statusText;
|
||||
QUrl pendingUrl;
|
||||
QString pendingString;
|
||||
QByteArray pendingData;
|
||||
|
||||
QQmlComponent* newWindowComponent;
|
||||
QQuickItem* newWindowParent;
|
||||
|
||||
QrcHandler qrcHandler;
|
||||
JsHandler jsHandler;
|
||||
FileHandler fileHandler;
|
||||
|
||||
bool rendering;
|
||||
bool overlapped;
|
||||
|
||||
QColor backgroundColor;
|
||||
QUrl baseUrl;
|
||||
QString title;
|
||||
QRect geometry;
|
||||
QString lastError;
|
||||
mutable bool visible;
|
||||
|
||||
protected Q_SLOTS:
|
||||
void applicationStateChanged(Qt::ApplicationState state);
|
||||
void onPageStarted();
|
||||
void onPageFinished();
|
||||
void onPageError();
|
||||
void onUrlChanged(const QUrl &url);
|
||||
void onAction(int);
|
||||
|
||||
protected:
|
||||
|
||||
void addToJavaScriptWindowObject(const QString& name, QObject* object);
|
||||
|
||||
QMutex renderMutex;
|
||||
QNetworkAccessManager *networkManager;
|
||||
AmneziaWebView *q_ptr;
|
||||
|
||||
private:
|
||||
QSignalMapper actions;
|
||||
QScopedPointer<AmneziaWebHistory> m_history;
|
||||
QScopedPointer<AmneziaWebViewSettings> m_settings;
|
||||
};
|
||||
|
||||
//!internal
|
||||
class NetworkAccessManager : public QNetworkAccessManager
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NetworkAccessManager(QNetworkAccessManager *manager, QObject *parent);
|
||||
explicit NetworkAccessManager(QObject *parent): QNetworkAccessManager(parent) { init(); }
|
||||
|
||||
public Q_SLOTS:
|
||||
void handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors);
|
||||
|
||||
protected:
|
||||
virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData = nullptr);
|
||||
private:
|
||||
void init();
|
||||
QNetworkAccessManager *manager;
|
||||
};
|
||||
|
||||
class DataReply : public QNetworkReply
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DataReply(QObject *parent, const QNetworkAccessManager::Operation operation, const QNetworkRequest &request, AmneziaWebView *view = nullptr);
|
||||
virtual ~DataReply() = default;
|
||||
|
||||
virtual void abort();
|
||||
virtual void close();
|
||||
virtual qint64 size() const;
|
||||
|
||||
virtual qint64 bytesAvailable() const;
|
||||
virtual bool isSequential() const;
|
||||
protected:
|
||||
virtual qint64 readData(char *data, qint64 maxSize);
|
||||
Q_INVOKABLE void setContent();
|
||||
|
||||
private:
|
||||
QByteArray content;
|
||||
qint64 offset;
|
||||
QPointer<AmneziaWebView> view;
|
||||
};
|
||||
|
||||
#endif
|
||||
494
client/core/webview/amneziawebview_webengine.cpp
Normal file
494
client/core/webview/amneziawebview_webengine.cpp
Normal file
@@ -0,0 +1,494 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QWebEngineUrlScheme>
|
||||
|
||||
#include "amneziawebview_webengine_p.h"
|
||||
#include "qrchandler.h"
|
||||
#include "filehandler.h"
|
||||
#include "amneziawebhistory.h"
|
||||
|
||||
typedef QMap<quintptr, AmneziaWebViewPrivate *> WebViews;
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(WebViews, g_webViews, ())
|
||||
|
||||
QrcHandler::QrcHandler()
|
||||
{}
|
||||
|
||||
FileHandler::FileHandler()
|
||||
{
|
||||
}
|
||||
|
||||
JsHandler::JsHandler(AmneziaWebView *host): _host(host), scriptObjectsInjected(false)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
JsHandler::~JsHandler() {}
|
||||
|
||||
WebPage::WebPage(QObject *parent)
|
||||
: QWebEnginePage(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WebPage::WebPage(QWebEngineProfile *profile, QObject *parent)
|
||||
: QWebEnginePage(profile, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WebPage::~WebPage()
|
||||
{
|
||||
disconnect(this);
|
||||
}
|
||||
|
||||
bool WebPage::acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame)
|
||||
{
|
||||
Q_UNUSED(type);
|
||||
// Always accept navigation requests to open links within WebView
|
||||
// This prevents opening links in external browser
|
||||
if (isMainFrame) {
|
||||
m_loadingUrl = url;
|
||||
emit loadingUrl(url);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QWebEnginePage *WebPage::createWindow(QWebEnginePage::WebWindowType type)
|
||||
{
|
||||
Q_UNUSED(type);
|
||||
// Return this page instead of creating a new window
|
||||
// This prevents opening new browser windows/tabs
|
||||
return this;
|
||||
}
|
||||
|
||||
void WebPage::handleUnsupportedContent(QNetworkReply *reply)
|
||||
{
|
||||
QString errorString = reply->errorString();
|
||||
|
||||
if (m_loadingUrl != reply->url()) {
|
||||
// sub resource of this page
|
||||
qWarning() << "Resource" << reply->url().toEncoded() << "has unknown Content-Type, will be ignored.";
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && !reply->header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||
errorString = "Unknown Content-Type";
|
||||
}
|
||||
|
||||
QFile file(QLatin1String(":/notfound.html"));
|
||||
bool isOpened = file.open(QIODevice::ReadOnly);
|
||||
Q_ASSERT(isOpened);
|
||||
Q_UNUSED(isOpened)
|
||||
|
||||
QString title = QCoreApplication::translate("webview", "Error loading page: %1").arg(reply->url().toString());
|
||||
QString html = QString(QLatin1String(file.readAll()))
|
||||
.arg(title)
|
||||
.arg(errorString)
|
||||
.arg(reply->url().toString());
|
||||
|
||||
QBuffer imageBuffer;
|
||||
imageBuffer.open(QBuffer::ReadWrite);
|
||||
QIcon icon = qApp->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0);
|
||||
QPixmap pixmap = icon.pixmap(QSize(32,32));
|
||||
if (pixmap.save(&imageBuffer, "PNG")) {
|
||||
html.replace(QLatin1String("IMAGE_BINARY_DATA_HERE"),
|
||||
QString(QLatin1String(imageBuffer.buffer().toBase64())));
|
||||
}
|
||||
|
||||
if (m_loadingUrl == reply->url()) {
|
||||
setHtml(html, reply->url());
|
||||
}
|
||||
}
|
||||
|
||||
const QString &LocalSchemeHandler::scheme()
|
||||
{
|
||||
static const QString localScheme("local");
|
||||
return localScheme;
|
||||
}
|
||||
|
||||
const QMimeDatabase &LocalSchemeHandler::mimeDatabase()
|
||||
{
|
||||
static const QMimeDatabase mimeDatabase;
|
||||
return mimeDatabase;
|
||||
}
|
||||
|
||||
void LocalSchemeHandler::requestStarted(QWebEngineUrlRequestJob *job)
|
||||
{
|
||||
const QByteArray requestMethod = job->requestMethod();
|
||||
const QUrl requestUrl = job->requestUrl();
|
||||
QString requestScheme = requestUrl.scheme();
|
||||
DesktopWebViewPrivate *d = qobject_cast<DesktopWebViewPrivate *>(parent());
|
||||
|
||||
if (!d) {
|
||||
job->fail(QWebEngineUrlRequestJob::RequestFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestScheme != LocalSchemeHandler::scheme()) {
|
||||
job->fail(QWebEngineUrlRequestJob::UrlInvalid);
|
||||
return;
|
||||
}
|
||||
|
||||
QBuffer *buffer = nullptr;
|
||||
QMimeType mimeType = mimeDatabase().mimeTypeForFile(requestUrl.fileName(), QMimeDatabase::MatchExtension);
|
||||
|
||||
if (d->fileHandler.canHandleUrl(requestUrl)) {
|
||||
const QByteArray content = d->fileHandler.dataForUrl(requestUrl);
|
||||
if (!content.isNull()) {
|
||||
buffer = new QBuffer();
|
||||
buffer->setData(content);
|
||||
}
|
||||
}
|
||||
if (!buffer) {
|
||||
|
||||
auto historyItems = d->history()->items();
|
||||
const auto it = std::find_if(historyItems.begin(), historyItems.end(),
|
||||
[requestUrl](const auto &item) {
|
||||
bool urlsMatch = item.url().matches(requestUrl, QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
bool hasData = (item.data().length() > 0);
|
||||
return urlsMatch && hasData; });
|
||||
|
||||
if (it != historyItems.end()) {
|
||||
buffer = new QBuffer();
|
||||
buffer->setData(it->data());
|
||||
mimeType = mimeDatabase().mimeTypeForName(it->mimeType());
|
||||
}
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
job->fail(QWebEngineUrlRequestJob::UrlNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(job, &QObject::destroyed, buffer, &QObject::deleteLater);
|
||||
job->reply(mimeType.name().toLocal8Bit(), buffer);
|
||||
}
|
||||
|
||||
DesktopWebViewPrivate::DesktopWebViewPrivate(AmneziaWebView* q): AmneziaWebViewPrivate(q)
|
||||
, viewId(reinterpret_cast<quintptr>(this))
|
||||
, containerWindow(0)
|
||||
, window(0)
|
||||
{
|
||||
m_localHandler = new LocalSchemeHandler(this);
|
||||
|
||||
container = new QWidget(0, Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Tool);
|
||||
container->setAttribute(Qt::WA_NativeWindow, true);
|
||||
container->setAttribute(Qt::WA_DontCreateNativeAncestors, true);
|
||||
|
||||
QWebEngineUrlScheme localScheme(LocalSchemeHandler::scheme().toUtf8());
|
||||
localScheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed |
|
||||
QWebEngineUrlScheme::SecureScheme |
|
||||
QWebEngineUrlScheme::ViewSourceAllowed |
|
||||
QWebEngineUrlScheme::ContentSecurityPolicyIgnored |
|
||||
QWebEngineUrlScheme::CorsEnabled |
|
||||
QWebEngineUrlScheme::FetchApiAllowed);
|
||||
QWebEngineUrlScheme::registerScheme(localScheme);
|
||||
|
||||
QWebEngineUrlScheme qrcScheme("qrc");
|
||||
qrcScheme.setFlags(QWebEngineUrlScheme::LocalScheme |
|
||||
QWebEngineUrlScheme::LocalAccessAllowed |
|
||||
QWebEngineUrlScheme::SecureScheme |
|
||||
QWebEngineUrlScheme::ContentSecurityPolicyIgnored |
|
||||
QWebEngineUrlScheme::CorsEnabled |
|
||||
QWebEngineUrlScheme::FetchApiAllowed);
|
||||
QWebEngineUrlScheme::registerScheme(qrcScheme);
|
||||
|
||||
view = new QWebEngineView(container);
|
||||
QWebEngineProfile *profile = new QWebEngineProfile(view);
|
||||
profile->installUrlSchemeHandler(LocalSchemeHandler::scheme().toUtf8(), m_localHandler);
|
||||
|
||||
m_page = new WebPage(profile, profile);
|
||||
m_page->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);
|
||||
connect(m_page, &QWebEnginePage::fileSystemAccessRequested, this, [](QWebEngineFileSystemAccessRequest request) {
|
||||
request.accept();
|
||||
});
|
||||
|
||||
view->settings()->setUnknownUrlSchemePolicy(QWebEngineSettings::AllowAllUnknownUrlSchemes);
|
||||
view->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
|
||||
view->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
|
||||
view->settings()->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
|
||||
view->settings()->setAttribute(QWebEngineSettings::AllowGeolocationOnInsecureOrigins, true);
|
||||
|
||||
view->setPage(m_page);
|
||||
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
container->setLayout(new QHBoxLayout(container));
|
||||
container->layout()->setSpacing(0);
|
||||
container->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
container->layout()->addWidget(view);
|
||||
|
||||
setBackgroundColor(backgroundColor);
|
||||
g_webViews->insert(viewId, this);
|
||||
|
||||
connect(view, SIGNAL(loadFinished(bool)), this, SIGNAL(loadFinished(bool)));
|
||||
connect(m_page, SIGNAL(loadFinished(bool)), this, SLOT(onLoadFinished(bool)), Qt::QueuedConnection);
|
||||
connect(m_page, SIGNAL(loadStarted()), this, SLOT(onPageStarted()), Qt::QueuedConnection);
|
||||
connect(view, SIGNAL(urlChanged(const QUrl &)), this, SLOT(onUrlChanged(const QUrl &)), Qt::QueuedConnection);
|
||||
container->createWinId();
|
||||
}
|
||||
|
||||
DesktopWebViewPrivate::~DesktopWebViewPrivate()
|
||||
{
|
||||
g_webViews->take(viewId);
|
||||
disconnect(this, SLOT(loadFinished(bool)));
|
||||
disconnect(this, SLOT(applicationStateChanged(Qt::ApplicationState)));
|
||||
disconnect(this, SLOT(onUrlChanged(const QUrl &)));
|
||||
disconnect(this, SLOT(onPageStarted()));
|
||||
disconnect(this, SLOT(onLoadFinished(bool)));
|
||||
|
||||
delete container;
|
||||
}
|
||||
|
||||
AmneziaWebViewPrivate *AmneziaWebViewPrivate::create(AmneziaWebView *q)
|
||||
{
|
||||
return new DesktopWebViewPrivate(q);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setWindowParent(QWindow *parent)
|
||||
{
|
||||
if (window) {
|
||||
window->removeEventFilter(this);
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
|
||||
containerWindow = qobject_cast<QWindow*>(container->windowHandle());
|
||||
containerWindow->setTransientParent(parent);
|
||||
parent->installEventFilter(this);
|
||||
|
||||
}
|
||||
window = parent;
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setBackgroundColor(const QColor backgroundColor)
|
||||
{
|
||||
this->backgroundColor = backgroundColor;
|
||||
QPalette p = container->palette();
|
||||
p.setColor(QPalette::Window, backgroundColor);
|
||||
container->setPalette(p);
|
||||
p = view->palette();
|
||||
p.setColor(QPalette::Window, backgroundColor);
|
||||
view->setPalette(p);
|
||||
emit backgroundColorChanged();
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
void DesktopWebViewPrivate::setScale(qreal scale)
|
||||
{
|
||||
Q_UNUSED(scale);
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
qreal DesktopWebViewPrivate::scale() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QIcon DesktopWebViewPrivate::icon() const
|
||||
{
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QSize DesktopWebViewPrivate::contentsSize() const
|
||||
{
|
||||
return QSize();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setGeometry(const QRect &geometry)
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
QQuickWindow *window = q->window();
|
||||
if (!window) return;
|
||||
QRect newGeometry = QRect(window->mapToGlobal(geometry.topLeft()), QSize(geometry.width(), geometry.height()));
|
||||
if (newGeometry.isValid() && container->geometry() != newGeometry ) {
|
||||
|
||||
this->geometry = geometry;
|
||||
container->setGeometry(newGeometry);
|
||||
container->updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
Q_UNUSED(obj);
|
||||
Q_Q(AmneziaWebView);
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::Move: {
|
||||
QMoveEvent *moveEvent = static_cast<QMoveEvent*>(event);
|
||||
QPoint p = q->mapToScene(QPointF(moveEvent->pos())).toPoint();
|
||||
container->move(p);
|
||||
|
||||
if (visible) {
|
||||
show();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case QEvent::Resize: {
|
||||
if (visible) {
|
||||
show();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case QEvent::WindowStateChange: {
|
||||
|
||||
Qt::WindowState state = window->windowState();
|
||||
if ((state == Qt::WindowMaximized) || (state == Qt::WindowFullScreen) || (state == Qt::WindowActive)) {
|
||||
show();
|
||||
}
|
||||
else {
|
||||
hide();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::hide()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (!q->window()) return;
|
||||
|
||||
if (visible) {
|
||||
QMetaObject::invokeMethod(container, "hide", Qt::QueuedConnection);
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::show()
|
||||
{
|
||||
Q_Q(AmneziaWebView);
|
||||
if (!q->window()) return;
|
||||
if (!visible) {
|
||||
|
||||
QMetaObject::invokeMethod(container, "show", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(container, "update", Qt::QueuedConnection);
|
||||
visible = true;
|
||||
}
|
||||
|
||||
containerWindow = qobject_cast<QWindow*>(container->windowHandle());
|
||||
|
||||
if ((containerWindow != nullptr) &&
|
||||
((qApp->topLevelWindows().at(0) != containerWindow) || !containerWindow->isVisible())) {
|
||||
|
||||
containerWindow->raise();
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::load(const QUrl& baseUrl)
|
||||
{
|
||||
QUrl url = baseUrl;
|
||||
if (!url.isValid()) {
|
||||
url = QUrl(QLatin1String("about:blank"));
|
||||
}
|
||||
view->load(url);
|
||||
history()->append(baseUrl);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl)
|
||||
{
|
||||
QUrl url = baseUrl;
|
||||
if (!url.isValid()) {
|
||||
url = QUrl(QLatin1String("about:blank"));
|
||||
}
|
||||
view->setContent(data, mimeType, url);
|
||||
history()->append(url, data, mimeType);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setHtml(const QString& html, const QUrl& baseUrl)
|
||||
{
|
||||
if(html.isNull()) return;
|
||||
QUrl url = baseUrl;
|
||||
if (!baseUrl.isValid())
|
||||
url = QUrl(QLatin1String("about:blank"));
|
||||
|
||||
view->setHtml(html, url);
|
||||
history()->append(url, html.toUtf8(), "text/html");
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::evaluateJavaScript(const QString& scriptSource)
|
||||
{
|
||||
view->page()->runJavaScript(scriptSource);
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::canGoBack() const
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebEnginePage::Back);
|
||||
bool can = (pageAction && pageAction->isEnabled());
|
||||
return can;
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::canGoForward() const
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebEnginePage::Forward);
|
||||
bool can = (pageAction && pageAction->isEnabled());
|
||||
return can;
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::back()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebEnginePage::Back);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::forward()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebEnginePage::Forward);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::reload()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebEnginePage::Reload);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::stop()
|
||||
{
|
||||
QAction *pageAction = view->pageAction(QWebEnginePage::Stop);
|
||||
if (pageAction)
|
||||
emit pageAction->trigger();
|
||||
}
|
||||
|
||||
bool DesktopWebViewPrivate::isLoading() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DesktopWebViewPrivate::innerHTML() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::onLoadFinished(bool success)
|
||||
{
|
||||
if (success) {
|
||||
QMetaObject::invokeMethod(this, "onPageFinished", Qt::QueuedConnection);
|
||||
}
|
||||
else {
|
||||
QMetaObject::invokeMethod(this, "onPageError", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setDefaultFontSize(int size)
|
||||
{
|
||||
view->settings()->setFontSize(QWebEngineSettings::DefaultFontSize, size);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setStandardFontFamily(const QString &family)
|
||||
{
|
||||
view->settings()->setFontFamily(QWebEngineSettings::StandardFont, family);
|
||||
}
|
||||
|
||||
void DesktopWebViewPrivate::setTextZoom(int percent)
|
||||
{
|
||||
Q_UNUSED(percent);
|
||||
}
|
||||
109
client/core/webview/amneziawebview_webengine_p.h
Normal file
109
client/core/webview/amneziawebview_webengine_p.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#ifndef AMNEZIAWEBVIEW_WEBENGINE_P_H
|
||||
#define AMNEZIAWEBVIEW_WEBENGINE_P_H
|
||||
|
||||
#include <QtWebEngineWidgets/QtWebEngineWidgets>
|
||||
|
||||
#include <QtWebEngineWidgets>
|
||||
#include <QWebEngineView>
|
||||
#include <QWebEnginePage>
|
||||
#include <QWebEngineUrlSchemeHandler>
|
||||
#include <QWebEngineUrlRequestJob>
|
||||
|
||||
#include "amneziawebview.h"
|
||||
#include "amneziawebview_p.h"
|
||||
|
||||
class DesktopWebViewPrivate;
|
||||
class LocalSchemeHandler;
|
||||
|
||||
class WebPage : public QWebEnginePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void loadingUrl(const QUrl &url);
|
||||
|
||||
public:
|
||||
explicit WebPage(QObject *parent = 0);
|
||||
explicit WebPage(QWebEngineProfile *profile, QObject *parent = 0);
|
||||
virtual ~WebPage();
|
||||
|
||||
protected:
|
||||
bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame) override;
|
||||
QWebEnginePage *createWindow(QWebEnginePage::WebWindowType type) override;
|
||||
|
||||
private slots:
|
||||
void handleUnsupportedContent(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
|
||||
friend class DesktopWebViewPrivate;
|
||||
QUrl m_loadingUrl;
|
||||
};
|
||||
|
||||
class DesktopWebViewPrivate : public AmneziaWebViewPrivate
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PUBLIC(AmneziaWebView)
|
||||
public:
|
||||
|
||||
explicit DesktopWebViewPrivate(AmneziaWebView* q);
|
||||
virtual ~DesktopWebViewPrivate();
|
||||
|
||||
virtual void setWindowParent(QWindow *parent);
|
||||
|
||||
virtual void setBackgroundColor(const QColor backgroundColor);
|
||||
virtual void show();
|
||||
virtual void hide();
|
||||
virtual void setGeometry(const QRect &);
|
||||
virtual QString innerHTML() const;
|
||||
virtual void load(const QUrl& url);
|
||||
virtual void setHtml(const QString& html, const QUrl& baseUrl = QUrl());
|
||||
virtual void setContent(const QByteArray& data, const QString& mimeType, const QUrl& baseUrl);
|
||||
virtual void evaluateJavaScript(const QString& scriptSource);
|
||||
virtual bool isLoading() const;
|
||||
virtual bool canGoBack() const;
|
||||
virtual bool canGoForward() const;
|
||||
virtual void back();
|
||||
virtual void forward();
|
||||
virtual void reload();
|
||||
virtual void stop();
|
||||
|
||||
virtual QIcon icon() const;
|
||||
virtual void setScale(qreal scale);
|
||||
virtual qreal scale() const;
|
||||
virtual QSize contentsSize() const;
|
||||
virtual void setDefaultFontSize(int size);
|
||||
virtual void setTextZoom(int percent);
|
||||
virtual void setStandardFontFamily(const QString &family);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
private slots:
|
||||
void onLoadFinished(bool);
|
||||
|
||||
private:
|
||||
quintptr viewId;
|
||||
QWebEngineView *view;
|
||||
QWidget *container;
|
||||
QWindow *containerWindow;
|
||||
QWindow *window;
|
||||
WebPage *m_page;
|
||||
LocalSchemeHandler *m_localHandler;
|
||||
};
|
||||
|
||||
class LocalSchemeHandler : public QWebEngineUrlSchemeHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalSchemeHandler(QObject *parent) : QWebEngineUrlSchemeHandler(parent)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
}
|
||||
|
||||
static const QString &scheme();
|
||||
static const QMimeDatabase &mimeDatabase();
|
||||
void requestStarted(QWebEngineUrlRequestJob *job) override;
|
||||
};
|
||||
|
||||
#endif // AMNEZIAWEBVIEW_WEBENGINE_P_H
|
||||
43
client/core/webview/filehandler.cpp
Normal file
43
client/core/webview/filehandler.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include "filehandler.h"
|
||||
#include "mimecache.h"
|
||||
|
||||
QList<QString> FileHandler::schemes()
|
||||
{
|
||||
static QList<QString> list = QList<QString>() << "file" << "local";
|
||||
return list;
|
||||
}
|
||||
|
||||
QByteArray FileHandler::dataForUrl(const QUrl &url) const
|
||||
{
|
||||
QUrl fileUrl = url;
|
||||
if (fileUrl.scheme() != "file") {
|
||||
fileUrl.setScheme("file");
|
||||
}
|
||||
QString requestUrl(fileUrl.toLocalFile());
|
||||
QFile resource(requestUrl);
|
||||
QByteArray buffer;
|
||||
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||
|
||||
buffer = resource.readAll();
|
||||
resource.close();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool FileHandler::canHandleUrl(const QUrl &url) const
|
||||
{
|
||||
if (schemes().contains(url.scheme().toLower()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
QString FileHandler::mimeTypeForUrl(const QUrl &url) const
|
||||
{
|
||||
return mimeTypeForExtension(url.path().section('.', -1));
|
||||
}
|
||||
18
client/core/webview/filehandler.h
Normal file
18
client/core/webview/filehandler.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef FILEHANDLER_H
|
||||
#define FILEHANDLER_H
|
||||
|
||||
class QString;
|
||||
class QByteArray;
|
||||
class QUrl;
|
||||
|
||||
class FileHandler
|
||||
{
|
||||
public:
|
||||
explicit FileHandler();
|
||||
static QList<QString> schemes();
|
||||
bool canHandleUrl(const QUrl &url) const;
|
||||
QByteArray dataForUrl(const QUrl &url) const;
|
||||
QString mimeTypeForUrl(const QUrl &url) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
10
client/core/webview/filehandler_android.cpp
Normal file
10
client/core/webview/filehandler_android.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include "filehandler.h"
|
||||
|
||||
FileHandler::FileHandler()
|
||||
{
|
||||
}
|
||||
135
client/core/webview/filehandler_ios.mm
Normal file
135
client/core/webview/filehandler_ios.mm
Normal file
@@ -0,0 +1,135 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <MobileCoreServices/UTType.h>
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QDebug>
|
||||
#include "mimecache.h"
|
||||
#include "filehandler.h"
|
||||
#define kProtocolFileScheme @"file"
|
||||
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
|
||||
@interface FileProtocol : NSURLProtocol
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
FileHandler::FileHandler()
|
||||
{
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
static bool protocolRegistered = false;
|
||||
if (!protocolRegistered) {
|
||||
[NSURLProtocol registerClass:[FileProtocol class]];
|
||||
protocolRegistered = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
|
||||
@implementation FileProtocol
|
||||
|
||||
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||
{
|
||||
NSString *scheme = request.URL.scheme;
|
||||
if ([kProtocolFileScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (NSString*)headFromHtml:(NSString*)html
|
||||
{
|
||||
if (!html) return nil;
|
||||
|
||||
NSString *head = nil;
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=<head>)[\\w\\W.]*(?=</head>)" options:NSRegularExpressionCaseInsensitive error:nil];
|
||||
NSTextCheckingResult *headResult = [regex firstMatchInString:html options:0 range:NSMakeRange(0, html.length)];
|
||||
|
||||
if (headResult && headResult.range.location != NSNotFound) {
|
||||
head = [html substringWithRange:[headResult range]];
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
+ (NSString *)charsetFromHtml:(NSString *)html
|
||||
{
|
||||
if (!html) return nil;
|
||||
|
||||
NSString *charset = nil;
|
||||
NSString *charsetPattern = @"((?<=charset=)\\s*[a-zA-Z0-9-]*)";
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:charsetPattern options: NSRegularExpressionCaseInsensitive error:nil];
|
||||
NSTextCheckingResult *charsetResult = [regex firstMatchInString:html options:kNilOptions range:NSMakeRange(0, [html length])];
|
||||
if (charsetResult && charsetResult.range.location != NSNotFound) {
|
||||
charset = [[html substringWithRange:[charsetResult range]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
charset = [charset lowercaseString];
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
- (void)startLoading
|
||||
{
|
||||
/* retrieve the current request. */
|
||||
NSURLRequest *request = [self request];
|
||||
NSString *url = [[request URL] path];
|
||||
NSString *mimeType = mimeTypeForExtension(QString::fromNSString(url.pathExtension)).toNSString();
|
||||
|
||||
QString requestUrl(QString("%1").arg(QString::fromNSString(url)));
|
||||
QFile resource(requestUrl);
|
||||
|
||||
NSData *pageData = nil;
|
||||
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||
|
||||
QByteArray buffer = resource.readAll();
|
||||
|
||||
//pageData = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||
pageData = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||
resource.close();
|
||||
}
|
||||
|
||||
if (pageData) {
|
||||
|
||||
NSString *encoding = @"utf-8";
|
||||
if ([mimeType isEqualToString:@"text/html"]) {
|
||||
|
||||
NSString *content = [[NSString alloc] initWithBytesNoCopy: (char* )[pageData bytes] length:pageData.length encoding:NSISOLatin1StringEncoding freeWhenDone:NO];
|
||||
|
||||
NSString *charset = [FileProtocol charsetFromHtml:[FileProtocol headFromHtml:content]];
|
||||
if (charset) {
|
||||
encoding = charset;
|
||||
}
|
||||
[content autorelease];
|
||||
}
|
||||
|
||||
NSURLResponse *response =[[NSURLResponse alloc]initWithURL:self.request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:[pageData length]
|
||||
textEncodingName:encoding];
|
||||
|
||||
|
||||
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||
[[self client] URLProtocol:self didLoadData:pageData];
|
||||
[[self client] URLProtocolDidFinishLoading:self];
|
||||
[response autorelease];
|
||||
}
|
||||
else {
|
||||
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopLoading
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
432
client/core/webview/jshandler.cpp
Normal file
432
client/core/webview/jshandler.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QMetaMethod>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtCore/QGenericArgument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include "amneziawebview_p.h"
|
||||
#include "mimecache.h"
|
||||
#include "jshandler.h"
|
||||
|
||||
QString JsHandler::scheme() const
|
||||
{
|
||||
return QString("js");
|
||||
}
|
||||
|
||||
QString JsHandler::host() const
|
||||
{
|
||||
qulonglong h = (qulonglong)(void*)_host;
|
||||
return QString("js%1").arg(h);
|
||||
}
|
||||
|
||||
QString JsHandler::scriptObjectsUrl() const
|
||||
{
|
||||
return QString("%1://%2/%3.js").arg(scheme()).arg(host()).arg(scriptObjectsId());
|
||||
}
|
||||
|
||||
QString JsHandler::scriptObjectsId() const
|
||||
{
|
||||
return QString("__scriptObjects__");
|
||||
}
|
||||
|
||||
QString JsHandler::scriptObjects() const
|
||||
{
|
||||
QString script;
|
||||
QList<QString> scripts = scriptParts.values();
|
||||
|
||||
for ( int i = 0; i < scripts.count(); i++) {
|
||||
script = QString("%1 %2").arg(script).arg(scripts.at(i)).trimmed();
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
void JsHandler::updateWebView()
|
||||
{
|
||||
if (!scriptObjectsInjected) {
|
||||
|
||||
QString injector = QString(
|
||||
" var script = document.getElementById(\"%1\"); "
|
||||
" if(script === null) { "
|
||||
" script = document.createElement('script'); "
|
||||
" script.type = \"text/javascript\"; "
|
||||
" script.id = \"%2\"; "
|
||||
" document.getElementsByTagName('head')[0].appendChild(script); "
|
||||
" } "
|
||||
" script.text = \"%3\"; "
|
||||
).arg(scriptObjectsId()).arg(scriptObjectsId()).arg(scriptObjects());
|
||||
|
||||
|
||||
|
||||
/*
|
||||
QString injector = QString(
|
||||
" var script = document.getElementById(\"%1\"); "
|
||||
" if(script === null) { "
|
||||
" script = document.createElement('script'); "
|
||||
" script.type = \"text/javascript\"; "
|
||||
" script.id = \"%2\"; "
|
||||
" document.getElementsByTagName('head')[0].appendChild(script); "
|
||||
" } "
|
||||
" script.src = \"%3\"; "
|
||||
).arg(scriptObjectsId()).arg(scriptObjectsId()).arg(scriptObjectsUrl());
|
||||
|
||||
*/
|
||||
|
||||
//убрали, так как теперь есть рабочий вебинспектор
|
||||
//_host->evaluateJavaScript(injector);
|
||||
scriptObjectsInjected = true;
|
||||
}
|
||||
}
|
||||
|
||||
void JsHandler::addToJavaScriptWindowObject(const QString& name, QObject* object)
|
||||
{
|
||||
windowObjects[name] = object;
|
||||
QString script = windowScriptObject(name, object);
|
||||
scriptParts[name] = script;
|
||||
}
|
||||
|
||||
bool JsHandler::canHandleUrl(const QUrl &url) const
|
||||
{
|
||||
if (scheme() == url.scheme() && url.host() == host())
|
||||
return true;
|
||||
|
||||
if (this->host() == url.host() && (url.scheme() == QString("http")))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString JsHandler::mimeTypeForUrl(const QUrl &url) const
|
||||
{
|
||||
if (scheme() == url.scheme() && url.host() == host())
|
||||
return mimeTypeForUrl(url);
|
||||
|
||||
if (this->host() == url.host() && (url.scheme() == QString("http")))
|
||||
return mimeTypeForExtension("json");
|
||||
|
||||
return QString("application/octet-stream");
|
||||
}
|
||||
|
||||
QString JsHandler::windowScriptObject(const QString& name, QObject* object) const
|
||||
{
|
||||
QString script = QString(" var %1 = {}; "
|
||||
" %2.responseText = null; "
|
||||
).arg(name).arg(name);
|
||||
if (object) {
|
||||
const QMetaObject *meta = object->metaObject();
|
||||
const QString prefix = QString("%1.").arg(name);
|
||||
|
||||
int methodCount = meta->methodCount();
|
||||
for (int i = 0; i < methodCount; i++) {
|
||||
QMetaMethod metaMethod = meta->method(i);
|
||||
|
||||
if ((metaMethod.access() == QMetaMethod::Public) && ((metaMethod.methodType() == QMetaMethod::Slot) ||
|
||||
(metaMethod.methodType() == QMetaMethod::Method)) ) {
|
||||
const QString methodName = QString("%1%2").arg(prefix).arg(QString(metaMethod.name()));
|
||||
|
||||
//Parameters
|
||||
QList<QByteArray> parametersNames = metaMethod.parameterNames();
|
||||
QString methodParameters = QString("");
|
||||
QString stringifyParameters = QString("");
|
||||
|
||||
for (int j = 0; j < parametersNames.count(); j++) {
|
||||
methodParameters += QString(parametersNames.at(j));
|
||||
|
||||
stringifyParameters += QString("'%1=' + encodeURIComponent(%2)").arg(QString(parametersNames.at(j)))
|
||||
.arg(QString(parametersNames.at(j)));
|
||||
|
||||
if(j < (parametersNames.count() -1)) {
|
||||
methodParameters += QString(", ");
|
||||
stringifyParameters += QString(" + '&' + ");
|
||||
}
|
||||
}
|
||||
if (stringifyParameters.length() > 0)
|
||||
stringifyParameters = QString(" + '?'+ %1").arg(stringifyParameters);
|
||||
|
||||
const QString methodUrl = QString("http://%1/%2").arg(host()).arg(methodName);
|
||||
//Body
|
||||
const QString bodyTemplate = QString(""
|
||||
" var obj = this; "
|
||||
" if (obj.responseText !== null) { "
|
||||
" var ret = eval( obj.responseText ); "
|
||||
" obj.responseText = null; "
|
||||
" return ret;"
|
||||
" }; "
|
||||
" var caller = arguments.callee.caller; "
|
||||
" var callerArgs = caller.arguments; "
|
||||
" var xhr = new XMLHttpRequest; "
|
||||
" xhr.onload=function(){ "
|
||||
" if (xhr.status == 200) { "
|
||||
" obj.responseText = xhr.responseText; "
|
||||
" if (obj.responseText == null) { "
|
||||
" obj.responceText = ''; "
|
||||
" } "
|
||||
" caller(callerArgs); "
|
||||
" }; "
|
||||
" }; "
|
||||
" xhr.open('GET', '%1'%2, true); "
|
||||
//" xhr.setRequestHeader('Access-Control-Allow-Origin', '*'); "
|
||||
//" xhr.setRequestHeader('Access-Control-Allow-Headers', 'Content-Type'); "
|
||||
" xhr.send(null);"
|
||||
).arg(methodUrl).arg(stringifyParameters);
|
||||
|
||||
|
||||
QString methodBody = QString("%1").arg(bodyTemplate);
|
||||
|
||||
script = script + QString("%1=function(%2){ %3 }; " )
|
||||
.arg(methodName)
|
||||
.arg(methodParameters)
|
||||
.arg(methodBody);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
|
||||
const QString createPopup = QString( ""
|
||||
" window.createPopup=function(){ "
|
||||
" var popup=document.createElement('iframe'), "
|
||||
" isShown=false, popupClicked=false; "
|
||||
" popup.src='about:blank'; "
|
||||
" popup.style.position='absolute'; "
|
||||
" popup.style.border='0px'; "
|
||||
" popup.style.display='none'; "
|
||||
" popup.addEventListener('load', function(e){ "
|
||||
" popup.document=(popup.contentWindow || popup.contentDocument); "
|
||||
" if(popup.document.document) popup.document=popup.document.document; "
|
||||
" }); "
|
||||
" document.body.appendChild(popup); "
|
||||
" var hidepopup=function(event){ "
|
||||
" if(isShown) "
|
||||
" setTimeout(function(){ "
|
||||
//" if(!popupClicked){ "
|
||||
" popup.hide(); "
|
||||
//" } "
|
||||
" popupClicked=false; "
|
||||
" }, 150); "
|
||||
" }; "
|
||||
" popup.show=function(x, y, w, h, pElement){ "
|
||||
" if(typeof(x) !== 'undefined'){ "
|
||||
" var elPos=[0, 0]; "
|
||||
" if(pElement) elPos=findPos(pElement); "
|
||||
" elPos[0]+=y, elPos[1]+=x; "
|
||||
" if(isNaN(w)) w=popup.document.scrollWidth; "
|
||||
" if(isNaN(h)) h=popup.document.scrollHeight; "
|
||||
" if(elPos[0] + w > document.body.clientWidth) elPos[0]=document.body.clientWidth - w - 5; "
|
||||
" if(elPos[1] + h > document.body.clientHeight) elPos[1]=document.body.clientHeight - h - 5; "
|
||||
" popup.style.left=elPos[0] + 'px'; "
|
||||
" popup.style.top=elPos[1] + 'px'; "
|
||||
" popup.style.width=(w + 'px'); "
|
||||
" popup.style.height=(h + 'px'); "
|
||||
" } "
|
||||
" popup.style.display='block'; "
|
||||
" isShown=true; "
|
||||
" }; "
|
||||
" popup.hide=function(){ "
|
||||
" isShown=false; "
|
||||
" popup.style.display='none'; "
|
||||
" }; "
|
||||
" window.addEventListener('click', hidepopup, true); "
|
||||
" window.addEventListener('blur', hidepopup, true); "
|
||||
" return popup; "
|
||||
" }; "
|
||||
" function findPos(obj, foundScrollLeft, foundScrollTop) { "
|
||||
" var curleft = 0; "
|
||||
" var curtop = 0; "
|
||||
" if(obj.offsetLeft) curleft += parseInt(obj.offsetLeft); "
|
||||
" if(obj.offsetTop) curtop += parseInt(obj.offsetTop); "
|
||||
" if(obj.scrollTop && obj.scrollTop > 0) { "
|
||||
" curtop -= parseInt(obj.scrollTop); "
|
||||
" foundScrollTop = true; "
|
||||
" } "
|
||||
" if(obj.scrollLeft && obj.scrollLeft > 0) { "
|
||||
" curleft -= parseInt(obj.scrollLeft); "
|
||||
" foundScrollLeft = true; "
|
||||
" } "
|
||||
" if(obj.offsetParent) { "
|
||||
" var pos = findPos(obj.offsetParent, foundScrollLeft, foundScrollTop); "
|
||||
" curleft += pos[0]; "
|
||||
" curtop += pos[1]; "
|
||||
" } else if(obj.ownerDocument) { "
|
||||
" var thewindow = obj.ownerDocument.defaultView; "
|
||||
" if(!thewindow && obj.ownerDocument.parentWindow) "
|
||||
" thewindow = obj.ownerDocument.parentWindow; "
|
||||
" if(thewindow) { "
|
||||
" if (!foundScrollTop && thewindow.scrollY && thewindow.scrollY > 0) curtop -= parseInt(thewindow.scrollY); "
|
||||
" if (!foundScrollLeft && thewindow.scrollX && thewindow.scrollX > 0) curleft -= parseInt(thewindow.scrollX); "
|
||||
" if(thewindow.frameElement) { "
|
||||
" var pos = findPos(thewindow.frameElement); "
|
||||
" curleft += pos[0]; "
|
||||
" curtop += pos[1]; "
|
||||
" } "
|
||||
" }"
|
||||
" }"
|
||||
" return [curleft,curtop]; "
|
||||
" } " );
|
||||
|
||||
|
||||
void JsHandler::init()
|
||||
{
|
||||
/*
|
||||
QString consoleObject = createPopup + QString (
|
||||
"function popup(msg){ "
|
||||
" var p = window.createPopup(); "
|
||||
" var pbody = p.document.body; "
|
||||
" pbody.style.backgroundColor='lime'; "
|
||||
" pbody.style.border='solid black 1px'; "
|
||||
" pbody.innerHTML=msg; "
|
||||
" p.show(NaN,NaN,NaN,NaN, document.body); }"
|
||||
" window.console={ "
|
||||
" log=function(msg){ popup(msg); }"
|
||||
" warning=function(msg){ popup(msg); }"
|
||||
" error=function(msg){ popup(msg); }"
|
||||
" info=function(msg){ popup(msg); }"
|
||||
" }");
|
||||
*/
|
||||
|
||||
QString consoleObject = QString ( ""
|
||||
" webviewPopup=function(msg){ "
|
||||
" var p = window.createPopup(); "
|
||||
" var pbody = p.document.body; "
|
||||
" pbody.style.backgroundColor='white'; "
|
||||
" pbody.style.border='solid black 1px'; "
|
||||
" pbody.innerHTML=msg + ' ' + document.body.clientWidth; "
|
||||
" p.show(0, 0, document.body.clientWidth, NaN, document.body); }; "
|
||||
" window.console.log=function(msg){ webviewPopup(msg); };"
|
||||
" window.console.warning=function(msg){ webviewPopup(msg); };"
|
||||
" window.console.error=function(msg){ webviewPopup(msg); };"
|
||||
" window.console.info=function(msg){ webviewPopup(msg); };"
|
||||
) + createPopup;
|
||||
/*
|
||||
QString object = QString( " var script = document.getElementById(\"consoleScriptObject\"); "
|
||||
" if(script === null) { "
|
||||
" script = document.createElement('script'); "
|
||||
" script.type = \"text/javascript\";"
|
||||
" script.text = \"%1\";"
|
||||
" script.id = \"consoleScriptObject\"; "
|
||||
" document.getElementsByTagName('head')[0].appendChild(script); "
|
||||
" } "
|
||||
).arg(consoleObject);
|
||||
*/
|
||||
|
||||
scriptParts["__console__"] = consoleObject;
|
||||
}
|
||||
|
||||
QByteArray JsHandler::dataForUrl(const QUrl &url) const
|
||||
{
|
||||
QByteArray buffer;
|
||||
QVariant ret = callMethodForUrl(url);
|
||||
if (ret.isValid()) {
|
||||
|
||||
QJsonValue value = QJsonValue::fromVariant(ret);
|
||||
QJsonArray jsonArray;
|
||||
jsonArray.append(value);
|
||||
QJsonDocument jsonDoc(jsonArray);
|
||||
buffer = jsonDoc.toJson();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
// Method call specified by url of type: http://scripthost/object.methodname?paramName1=paramValue1¶mName2=paramValue2&...
|
||||
QVariant JsHandler::callMethodForUrl(const QUrl &url) const
|
||||
{
|
||||
qDebug() << url.toString();
|
||||
qDebug() << "Path: " << url.path();
|
||||
qDebug() << "Query: " << url.query();
|
||||
|
||||
QVariant ret;
|
||||
QStringList objectPath = url.path().trimmed().remove('/').split(".");
|
||||
QStringList query;
|
||||
if (url.query().trimmed().length() > 0)
|
||||
query = url.query().trimmed().split("&");
|
||||
QList<QGenericArgument> arguments;
|
||||
QList<QByteArray> parametersNames;
|
||||
QList<QByteArray> parametersTypes;
|
||||
QString methodName;
|
||||
QString signature;
|
||||
|
||||
AmneziaWebViewPrivate *d = AmneziaWebViewPrivate::get(_host);
|
||||
|
||||
if (d && objectPath.count() == 2) {
|
||||
qDebug() << "Called method: " << objectPath[0] << "."<< objectPath[1];
|
||||
QObject *object = d->jsHandler.windowObjects.value(objectPath[0]);
|
||||
if (object) {
|
||||
int methodCount = object->metaObject()->methodCount();
|
||||
int methodIndex = -1;
|
||||
for(int i = 0; i < methodCount; i++) {
|
||||
const QMetaMethod method = object->metaObject()->method(i);
|
||||
parametersNames = method.parameterNames();
|
||||
methodName = method.name();
|
||||
if ((query.count() == parametersNames.count()) && (methodName == objectPath[1])) {
|
||||
methodIndex = i;
|
||||
parametersTypes = method.parameterTypes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (methodIndex >= 0) {
|
||||
const QMetaMethod method = object->metaObject()->method(methodIndex);
|
||||
QVariantList params;
|
||||
for (int i = 0; i < query.count(); i++) {
|
||||
QStringList param = query[i].split('=');
|
||||
|
||||
params.append(QVariant(QString(param[1].toLocal8Bit())));
|
||||
QGenericArgument arg(param[0].toLocal8Bit().constData(), ¶ms[i]);
|
||||
arguments.append(arg);
|
||||
}
|
||||
switch (arguments.count()) {
|
||||
case 1:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2]);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||
arguments[3]);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||
arguments[3], arguments[4]);
|
||||
break;
|
||||
case 6:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||
arguments[3], arguments[4], arguments[5]);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||
arguments[3], arguments[4], arguments[5], arguments[6]);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret), arguments[0], arguments[1], arguments[2],
|
||||
arguments[3], arguments[4], arguments[5], arguments[6], arguments[7]);
|
||||
break;
|
||||
|
||||
default:
|
||||
method.invoke(object, Qt::DirectConnection, Q_RETURN_ARG(QVariant, ret));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
41
client/core/webview/jshandler.h
Normal file
41
client/core/webview/jshandler.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef JSHANDLER_H
|
||||
#define JSHANDLER_H
|
||||
|
||||
class QUrl;
|
||||
class QVariant;
|
||||
class AmneziaWebView;
|
||||
|
||||
class JsHandler
|
||||
{
|
||||
friend class AmneziaWebView;
|
||||
|
||||
public:
|
||||
explicit JsHandler(AmneziaWebView *host);
|
||||
virtual ~JsHandler();
|
||||
|
||||
bool canHandleUrl(const QUrl &url) const;
|
||||
QByteArray dataForUrl(const QUrl &url) const;
|
||||
void addToJavaScriptWindowObject(const QString& name, QObject* object);
|
||||
QString mimeTypeForUrl(const QUrl &url) const;
|
||||
|
||||
void updateWebView();
|
||||
QString host() const;
|
||||
QString scheme() const;
|
||||
QString scriptObjectsUrl() const;
|
||||
QString scriptObjectsId() const;
|
||||
QString scriptObjects() const;
|
||||
|
||||
private:
|
||||
|
||||
QVariant callMethodForUrl(const QUrl &url) const;
|
||||
QString windowScriptObject(const QString& name, QObject* object) const;
|
||||
|
||||
void init();
|
||||
AmneziaWebView *_host;
|
||||
bool scriptObjectsInjected;
|
||||
|
||||
QHash<QString, QObject*> windowObjects;
|
||||
QHash<QString, QString> scriptParts;
|
||||
};
|
||||
|
||||
#endif
|
||||
24
client/core/webview/jshandler_android.cpp
Normal file
24
client/core/webview/jshandler_android.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QMetaMethod>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtCore/QGenericArgument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include "amneziawebview_p.h"
|
||||
#include "jshandler.h"
|
||||
#include "mimecache.h"
|
||||
|
||||
JsHandler::JsHandler(AmneziaWebView *host): _host(host)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
JsHandler::~JsHandler() {}
|
||||
|
||||
185
client/core/webview/jshandler_ios.mm
Normal file
185
client/core/webview/jshandler_ios.mm
Normal file
@@ -0,0 +1,185 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <MobileCoreServices/UTType.h>
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QRect>
|
||||
#include <QColor>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QMetaMethod>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtCore/QGenericArgument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include "amneziawebview_p.h"
|
||||
#include "mimecache.h"
|
||||
#import "jshandler.h"
|
||||
|
||||
typedef QHash<QString, AmneziaWebView*> JavaScriptHosts;
|
||||
Q_GLOBAL_STATIC(JavaScriptHosts, hosts);
|
||||
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
|
||||
@interface JsProtocol : NSURLProtocol
|
||||
+ (NSString*) requestVarsKey;
|
||||
@end
|
||||
|
||||
@interface NSURLRequest (JsProtocol)
|
||||
- (NSDictionary *)requestVars;
|
||||
@end
|
||||
|
||||
@interface NSMutableURLRequest (JsProtocol)
|
||||
- (void)setRequestVars:(NSDictionary *)vars;
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
JsHandler::JsHandler(AmneziaWebView *h): _host(h)
|
||||
, scriptObjectsInjected(false)
|
||||
{
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
static bool protocolRegistered = false;
|
||||
if (!protocolRegistered) {
|
||||
[NSURLProtocol registerClass:[JsProtocol class]];
|
||||
protocolRegistered = true;
|
||||
}
|
||||
#endif
|
||||
QString key = host();
|
||||
if (!hosts()->keys().contains(key)) {
|
||||
hosts()->insert(key, h);
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
||||
JsHandler::~JsHandler()
|
||||
{
|
||||
QString key = host();
|
||||
if (hosts()->keys().contains(key)) {
|
||||
hosts()->remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
|
||||
@implementation NSURLRequest (JsProtocol)
|
||||
|
||||
- (NSDictionary *)requestVars {
|
||||
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||
return [NSURLProtocol propertyForKey:[JsProtocol requestVarsKey] inRequest:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSMutableURLRequest (JsProtocol)
|
||||
|
||||
- (void)setRequestVars:(NSDictionary *)requestVars {
|
||||
|
||||
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||
|
||||
NSDictionary *specialVarsCopy = [requestVars copy];
|
||||
[NSURLProtocol setProperty:specialVarsCopy forKey:[JsProtocol requestVarsKey] inRequest:self];
|
||||
[specialVarsCopy release];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation JsProtocol
|
||||
|
||||
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||
{
|
||||
//NSString *url = request.URL.absoluteString;
|
||||
QString host = QString::fromNSString(request.URL.host).toLower();
|
||||
if (hosts()->keys().contains(host))
|
||||
return YES;
|
||||
|
||||
//NSLog(@"Requested Url: %@", url);
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (NSString*) requestVarsKey
|
||||
{
|
||||
return @"requestVars";
|
||||
}
|
||||
|
||||
- (void)startLoading
|
||||
{
|
||||
QString host = QString::fromNSString(self.request.URL.host);
|
||||
AmneziaWebViewPrivate *d = AmneziaWebViewPrivate::get(hosts()->value(host));
|
||||
NSURLRequest *request = [self request];
|
||||
|
||||
if ([request.URL.absoluteString isEqualToString: d->jsHandler.scriptObjectsUrl().toNSString()]) {
|
||||
|
||||
//NSString *mimeType = mimeTypeForExtension(QString::fromNSString(request.URL.path.pathExtension)).toNSString();
|
||||
|
||||
NSString *mimeType = d->jsHandler.mimeTypeForUrl(QUrl::fromNSURL(request.URL)).toNSString();
|
||||
QByteArray buffer = d->jsHandler.scriptObjects().toLocal8Bit();
|
||||
|
||||
//NSData *data = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||
NSData *data = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||
|
||||
NSURLResponse *response =[[NSURLResponse alloc]initWithURL:self.request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:[data length]
|
||||
textEncodingName:@"utf-8"];
|
||||
|
||||
|
||||
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||
[[self client] URLProtocol:self didLoadData:data];
|
||||
[[self client] URLProtocolDidFinishLoading:self];
|
||||
[response autorelease];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( d && [request.URL.scheme isEqualToString:@"http"]
|
||||
&& [request.URL.host isEqualToString:host.toNSString()]) {
|
||||
|
||||
const QByteArray buffer = d->dataForUrl(QUrl::fromNSURL(request.URL));
|
||||
//NSData *data = data = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||
NSData *data = data = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||
|
||||
//NSString *mimeType = mimeTypeForExtension(QString("json")).toNSString();
|
||||
NSString *mimeType = d->jsHandler.mimeTypeForUrl(QUrl::fromNSURL(request.URL)).toNSString();
|
||||
|
||||
NSDictionary *headers = @{@"Access-Control-Allow-Origin" : @"*",
|
||||
@"Access-Control-Allow-Headers" : @"Content-Type",
|
||||
@"Cache-Control" : @"no-cache",
|
||||
@"Content-Type" : [NSString stringWithFormat:@"%@; %@", mimeType, @"charset=UTF-8"] };
|
||||
|
||||
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc] initWithURL:request.URL
|
||||
statusCode:200
|
||||
HTTPVersion:@"HTTP/1.1"
|
||||
headerFields:headers];
|
||||
|
||||
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||
[self.client URLProtocol:self didLoadData:data];
|
||||
[self.client URLProtocolDidFinishLoading:self];
|
||||
[response autorelease];
|
||||
return;
|
||||
}
|
||||
else {
|
||||
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopLoading
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
274
client/core/webview/mimecache.cpp
Normal file
274
client/core/webview/mimecache.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
#include "mimecache.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QMimeDatabase>
|
||||
#include <QtCore/QMimeType>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
typedef QHash<QString, QString> MimeTypes;
|
||||
Q_GLOBAL_STATIC(MimeTypes, cache)
|
||||
Q_GLOBAL_STATIC(QMimeDatabase, mimeDatabase)
|
||||
|
||||
|
||||
void initCache()
|
||||
{
|
||||
if (cache()->isEmpty()) {
|
||||
|
||||
cache()->insert("323", "text/h323");
|
||||
cache()->insert("*", "application/octet-stream");
|
||||
cache()->insert("acx", "application/internet-property-stream");
|
||||
cache()->insert("ai", "application/postscript");
|
||||
cache()->insert("aif", "audio/x-aiff");
|
||||
cache()->insert("aifc", "audio/x-aiff");
|
||||
cache()->insert("aiff", "audio/x-aiff");
|
||||
cache()->insert("asf", "video/x-ms-asf");
|
||||
cache()->insert("asr", "video/x-ms-asf");
|
||||
cache()->insert("asx", "video/x-ms-asf");
|
||||
cache()->insert("au", "audio/basic");
|
||||
cache()->insert("avi", "video/x-msvideo");
|
||||
cache()->insert("axs", "application/olescript");
|
||||
cache()->insert("bas", "text/plain");
|
||||
cache()->insert("bcpio", "application/x-bcpio");
|
||||
cache()->insert("bin", "application/octet-stream");
|
||||
cache()->insert("bmp", "image/bmp");
|
||||
cache()->insert("c", "text/plain");
|
||||
cache()->insert("cat", "application/vnd.ms-pkiseccat");
|
||||
cache()->insert("cdf", "application/x-cdf");
|
||||
cache()->insert("cdf", "application/x-netcdf");
|
||||
cache()->insert("cer", "application/x-x509-ca-cert""cer");
|
||||
cache()->insert("class", "application/octet-stream");
|
||||
cache()->insert("clp", "application/x-msclip");
|
||||
cache()->insert("cmx", "image/x-cmx");
|
||||
cache()->insert("cod", "image/cis-cod");
|
||||
cache()->insert("cpio", "application/x-cpio");
|
||||
cache()->insert("crd", "application/x-mscardfile");
|
||||
cache()->insert("crl", "application/pkix-crl");
|
||||
cache()->insert("crt", "application/x-x509-ca-cert");
|
||||
cache()->insert("csh", "application/x-csh");
|
||||
cache()->insert("css", "text/css");
|
||||
cache()->insert("dcr", "application/x-director");
|
||||
cache()->insert("der", "application/x-x509-ca-cert");
|
||||
cache()->insert("dir", "application/x-director");
|
||||
cache()->insert("dll", "application/x-msdownload");
|
||||
cache()->insert("dms", "application/octet-stream");
|
||||
cache()->insert("doc", "application/msword");
|
||||
cache()->insert("dot", "application/msword");
|
||||
cache()->insert("dvi", "application/x-dvi");
|
||||
cache()->insert("dxr", "application/x-director");
|
||||
cache()->insert("eot", "application/vnd.ms-fontobject");
|
||||
cache()->insert("eps", "application/postscript");
|
||||
cache()->insert("etx", "text/x-setext");
|
||||
cache()->insert("evy", "application/envoy");
|
||||
cache()->insert("exe", "application/octet-stream");
|
||||
cache()->insert("fif", "application/fractals");
|
||||
cache()->insert("flr", "x-world/x-vrml");
|
||||
cache()->insert("gif", "image/gif");
|
||||
cache()->insert("gtar", "application/x-gtar");
|
||||
cache()->insert("gz", "application/x-gzip");
|
||||
cache()->insert("h", "text/plain");
|
||||
cache()->insert("hdf", "application/x-hdf");
|
||||
cache()->insert("hlp", "application/winhlp");
|
||||
cache()->insert("hqx", "application/mac-binhex40");
|
||||
cache()->insert("hta", "application/hta");
|
||||
cache()->insert("htc", "text/x-component");
|
||||
cache()->insert("htm", "text/html");
|
||||
cache()->insert("html", "text/html");
|
||||
cache()->insert("htt", "text/webviewhtml");
|
||||
cache()->insert("ico", "image/x-icon");
|
||||
cache()->insert("ief", "image/ief");
|
||||
cache()->insert("iii", "application/x-iphone");
|
||||
cache()->insert("ins", "application/x-internet-signup");
|
||||
cache()->insert("isp", "application/x-internet-signup");
|
||||
cache()->insert("jfif", "image/pipeg");
|
||||
cache()->insert("jpe", "image/jpeg");
|
||||
cache()->insert("jpeg", "image/jpeg");
|
||||
cache()->insert("jpg", "image/jpeg");
|
||||
cache()->insert("js", "application/x-javascript");
|
||||
cache()->insert("json", "application/json");
|
||||
cache()->insert("latex", "application/x-latex");
|
||||
cache()->insert("lha", "application/octet-stream");
|
||||
cache()->insert("lsf", "video/x-la-asf");
|
||||
cache()->insert("lsx", "video/x-la-asf");
|
||||
cache()->insert("lzh", "application/octet-stream");
|
||||
cache()->insert("m13", "application/x-msmediaview");
|
||||
cache()->insert("m14", "application/x-msmediaview");
|
||||
cache()->insert("m3u", "audio/x-mpegurl");
|
||||
cache()->insert("m4v", "video/x-m4v");
|
||||
cache()->insert("man", "application/x-troff-man");
|
||||
cache()->insert("mdb", "application/x-msaccess");
|
||||
cache()->insert("me", "application/x-troff-me");
|
||||
cache()->insert("mht", "message/rfc822");
|
||||
cache()->insert("mhtml", "message/rfc822");
|
||||
cache()->insert("mid", "audio/mid");
|
||||
cache()->insert("mny", "application/x-msmoney");
|
||||
cache()->insert("mov", "video/quicktime");
|
||||
cache()->insert("movie", "video/x-sgi-movie""movie");
|
||||
cache()->insert("mp2", "video/mpeg");
|
||||
cache()->insert("mp3", "audio/mpeg");
|
||||
cache()->insert("mpa", "video/mpeg");
|
||||
cache()->insert("mpe", "video/mpeg");
|
||||
cache()->insert("mpeg", "video/mpeg");
|
||||
cache()->insert("mpg", "video/mpeg");
|
||||
cache()->insert("mpp", "application/vnd.ms-project");
|
||||
cache()->insert("mpv2", "video/mpeg");
|
||||
cache()->insert("ms", "application/x-troff-ms");
|
||||
cache()->insert("msg", "application/vnd.ms-outlook");
|
||||
cache()->insert("mvb", "application/x-msmediaview");
|
||||
cache()->insert("nc", "application/x-netcdf");
|
||||
cache()->insert("nws", "message/rfc822");
|
||||
cache()->insert("oda", "application/oda");
|
||||
cache()->insert("otf", "application/font-sfnt");
|
||||
cache()->insert("p10", "application/pkcs10");
|
||||
cache()->insert("p12", "application/x-pkcs12");
|
||||
cache()->insert("p7b", "application/x-pkcs7-certificates");
|
||||
cache()->insert("p7c", "application/x-pkcs7-mime");
|
||||
cache()->insert("p7m", "application/x-pkcs7-mime");
|
||||
cache()->insert("p7r", "application/x-pkcs7-certreqresp");
|
||||
cache()->insert("p7s", "application/x-pkcs7-signature");
|
||||
cache()->insert("pbm", "image/x-portable-bitmap");
|
||||
cache()->insert("pdf", "application/pdf");
|
||||
cache()->insert("pfx", "application/x-pkcs12");
|
||||
cache()->insert("pgm", "image/x-portable-graymap");
|
||||
cache()->insert("pko", "application/ynd.ms-pkipko");
|
||||
cache()->insert("pma", "application/x-perfmon");
|
||||
cache()->insert("pmc", "application/x-perfmon");
|
||||
cache()->insert("pml", "application/x-perfmon");
|
||||
cache()->insert("pmr", "application/x-perfmon");
|
||||
cache()->insert("pmw", "application/x-perfmon");
|
||||
cache()->insert("pnm", "image/x-portable-anymap");
|
||||
cache()->insert("pot", "application/vnd.ms-powerpoint");
|
||||
cache()->insert("ppm", "image/x-portable-pixmap");
|
||||
cache()->insert("pps", "application/vnd.ms-powerpoint");
|
||||
cache()->insert("ppt", "application/vnd.ms-powerpoint");
|
||||
cache()->insert("prf", "application/pics-rules");
|
||||
cache()->insert("ps", "application/postscript");
|
||||
cache()->insert("pub", "application/x-mspublisher");
|
||||
cache()->insert("qt", "video/quicktime");
|
||||
cache()->insert("ra", "audio/x-pn-realaudio");
|
||||
cache()->insert("ram", "audio/x-pn-realaudio");
|
||||
cache()->insert("ras", "image/x-cmu-raster");
|
||||
cache()->insert("rgb", "image/x-rgb");
|
||||
cache()->insert("rmi", "audio/mid");
|
||||
cache()->insert("roff", "application/x-troff");
|
||||
cache()->insert("rtf", "application/rtf""rtf");
|
||||
cache()->insert("rtx", "text/richtext""rtx");
|
||||
cache()->insert("scd", "application/x-msschedule");
|
||||
cache()->insert("sct", "text/scriptlet");
|
||||
cache()->insert("setpay", "application/set-payment-initiation");
|
||||
cache()->insert("setreg", "application/set-registration-initiation");
|
||||
cache()->insert("sh", "application/x-sh");
|
||||
cache()->insert("shar", "application/x-shar");
|
||||
cache()->insert("sit", "application/x-stuffit");
|
||||
cache()->insert("snd", "audio/basic");
|
||||
cache()->insert("spc", "application/x-pkcs7-certificates");
|
||||
cache()->insert("spl", "application/futuresplash");
|
||||
cache()->insert("src", "application/x-wais-source");
|
||||
cache()->insert("sst", "application/vnd.ms-pkicertstore");
|
||||
cache()->insert("stl", "application/vnd.ms-pkistl");
|
||||
cache()->insert("stm", "text/html");
|
||||
cache()->insert("sv4cpio", "application/x-sv4cpio");
|
||||
cache()->insert("sv4crc", "application/x-sv4crc");
|
||||
cache()->insert("svg", "image/svg+xml");
|
||||
cache()->insert("swf", "application/x-shockwave-flash");
|
||||
cache()->insert("t", "application/x-troff");
|
||||
cache()->insert("tar", "application/x-tar");
|
||||
cache()->insert("tcl", "application/x-tcl");
|
||||
cache()->insert("tex", "application/x-tex");
|
||||
cache()->insert("texi", "application/x-texinfo");
|
||||
cache()->insert("texinfo", "application/x-texinfo");
|
||||
cache()->insert("tgz", "application/x-compressed");
|
||||
cache()->insert("tif", "image/tiff");
|
||||
cache()->insert("tiff", "image/tiff");
|
||||
cache()->insert("tr", "application/x-troff");
|
||||
cache()->insert("trm", "application/x-msterminal");
|
||||
cache()->insert("tsv", "text/tab-separated-values");
|
||||
cache()->insert("txt", "text/plain");
|
||||
cache()->insert("ttf", "application/font-sfnt");
|
||||
cache()->insert("uls", "text/iuls");
|
||||
cache()->insert("ustar", "application/x-ustar");
|
||||
cache()->insert("vcf", "text/x-vcard");
|
||||
cache()->insert("vrml", "x-world/x-vrml");
|
||||
cache()->insert("wav", "audio/x-wav");
|
||||
cache()->insert("wcm", "application/vnd.ms-works");
|
||||
cache()->insert("wdb", "application/vnd.ms-works");
|
||||
cache()->insert("wks", "application/vnd.ms-works");
|
||||
cache()->insert("wmf", "application/x-msmetafile");
|
||||
cache()->insert("woff", "application/font-woff");
|
||||
cache()->insert("wps", "application/vnd.ms-works");
|
||||
cache()->insert("wri", "application/x-mswrite");
|
||||
cache()->insert("wrl", "x-world/x-vrml");
|
||||
cache()->insert("wrz", "x-world/x-vrml");
|
||||
cache()->insert("xaf", "x-world/x-vrml");
|
||||
cache()->insert("xbm", "image/x-xbitmap");
|
||||
cache()->insert("xla", "application/vnd.ms-excel");
|
||||
cache()->insert("xlc", "application/vnd.ms-excel");
|
||||
cache()->insert("xlm", "application/vnd.ms-excel");
|
||||
cache()->insert("xls", "application/vnd.ms-excel");
|
||||
cache()->insert("xlt", "application/vnd.ms-excel");
|
||||
cache()->insert("xlw", "application/vnd.ms-excel");
|
||||
cache()->insert("xof", "x-world/x-vrml");
|
||||
cache()->insert("xpm", "image/x-xpixmap");
|
||||
cache()->insert("xwd", "image/x-xwindowdump");
|
||||
cache()->insert("z", "application/x-compress");
|
||||
cache()->insert("zip", "application/zip");
|
||||
}
|
||||
}
|
||||
|
||||
QString mimeTypeForExtension(const QString &extension)
|
||||
{
|
||||
initCache();
|
||||
QString ext = extension;
|
||||
|
||||
const int lastDot = ext.lastIndexOf(QLatin1Char('.'));
|
||||
if (lastDot != -1) {
|
||||
const int extLength = ext.length() - lastDot - 1;
|
||||
ext = ext.right(extLength).toLower();
|
||||
}
|
||||
|
||||
QString mimeType = cache()->value(ext);
|
||||
|
||||
if (mimeType.isNull()) {
|
||||
mimeType = QString("application/octet-stream");
|
||||
}
|
||||
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
QString mimeTypeForUrl(const QUrl &url)
|
||||
{
|
||||
initCache();
|
||||
QString path = url.path();
|
||||
|
||||
QString extension;
|
||||
QString mimeType;
|
||||
|
||||
const int lastDot = path.lastIndexOf(QLatin1Char('.'));
|
||||
if (lastDot != -1) {
|
||||
const int extLength = path.length() - lastDot - 1;
|
||||
extension = path.right(extLength).toLower();
|
||||
}
|
||||
|
||||
if (!extension.isNull()) {
|
||||
mimeType = cache()->value(extension);
|
||||
}
|
||||
if (mimeType.isNull()) {
|
||||
QMimeType mime = mimeDatabase()->mimeTypeForUrl(url);
|
||||
if (mime.isValid()) {
|
||||
mimeType = mime.name();
|
||||
if(!extension.isNull()) {
|
||||
cache()->insert(extension, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mimeType.isNull()) {
|
||||
mimeType = QString("application/octet-stream");
|
||||
}
|
||||
|
||||
qDebug() << mimeType;
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
10
client/core/webview/mimecache.h
Normal file
10
client/core/webview/mimecache.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef MIMECACHE_H
|
||||
#define MIMECACHE_H
|
||||
|
||||
class QString;
|
||||
class QUrl;
|
||||
|
||||
QString mimeTypeForExtension(const QString &extension);
|
||||
QString mimeTypeForUrl(const QUrl &url);
|
||||
|
||||
#endif
|
||||
66
client/core/webview/pch.h
Normal file
66
client/core/webview/pch.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
/*
|
||||
* This is a precompiled header file for use in Xcode / Mac GCC /
|
||||
* GCC >= 3.4 / VC to greatly speed the building of Qt. It may also be
|
||||
* of use to people developing their own project, but it is probably
|
||||
* better to define your own header. Use of this header is currently
|
||||
* UNSUPPORTED.
|
||||
*/
|
||||
|
||||
|
||||
#if defined __cplusplus
|
||||
// for rand_s, _CRT_RAND_S must be #defined before #including stdlib.h.
|
||||
// put it at the beginning so some indirect inclusion doesn't break it
|
||||
#ifndef _CRT_RAND_S
|
||||
#define _CRT_RAND_S
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <qglobal.h>
|
||||
#ifdef Q_OS_WIN
|
||||
# define _POSIX_
|
||||
# include <limits.h>
|
||||
# undef _POSIX_
|
||||
#endif
|
||||
#include <QtCore/QtCore>
|
||||
#ifndef Q_OS_WIN
|
||||
#include <QtQuick/QtQuick>
|
||||
#endif
|
||||
#endif
|
||||
17
client/core/webview/plugin.cpp
Normal file
17
client/core/webview/plugin.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "plugin.h"
|
||||
|
||||
#include "amneziawebview.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
void WebViewPlugin::registerTypes(const char* uri)
|
||||
{
|
||||
#ifndef QT_NO_ACTION
|
||||
qmlRegisterAnonymousType<QAction>(uri, 1);
|
||||
#endif
|
||||
qmlRegisterAnonymousType<AmneziaWebViewSettings>(uri, 1);
|
||||
qmlRegisterType<AmneziaWebView>(uri, 1, 0, "AmneziaWebView");
|
||||
qmlRegisterRevision<AmneziaWebView, 0>("AmneziaWebView", 1, 0);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
15
client/core/webview/plugin.h
Normal file
15
client/core/webview/plugin.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <QQmlExtensionPlugin>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class WebViewPlugin : public QQmlExtensionPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface" FILE "webview.json")
|
||||
Q_INTERFACES(QQmlExtensionInterface)
|
||||
|
||||
public:
|
||||
void registerTypes(const char* uri) override;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
2
client/core/webview/qmldir
Normal file
2
client/core/webview/qmldir
Normal file
@@ -0,0 +1,2 @@
|
||||
module AmneziaWebView
|
||||
plugin webview
|
||||
37
client/core/webview/qrchandler.cpp
Normal file
37
client/core/webview/qrchandler.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include "qrchandler.h"
|
||||
#include "mimecache.h"
|
||||
|
||||
QString QrcHandler::scheme()
|
||||
{
|
||||
return QString("qrc");
|
||||
}
|
||||
|
||||
QByteArray QrcHandler::dataForUrl(const QUrl &url) const
|
||||
{
|
||||
QString requestUrl(QString(":/%1").arg(url.path()));
|
||||
QFile resource(requestUrl);
|
||||
QByteArray buffer;
|
||||
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||
|
||||
buffer = resource.readAll();
|
||||
resource.close();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool QrcHandler::canHandleUrl(const QUrl &url) const
|
||||
{
|
||||
if (scheme() == url.scheme())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
QString QrcHandler::mimeTypeForUrl(const QUrl &url) const
|
||||
{
|
||||
return mimeTypeForExtension(url.path().section('.', -1));
|
||||
}
|
||||
18
client/core/webview/qrchandler.h
Normal file
18
client/core/webview/qrchandler.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef QRCHANDLER_H
|
||||
#define QRCHANDLER_H
|
||||
|
||||
class QString;
|
||||
class QByteArray;
|
||||
class QUrl;
|
||||
|
||||
class QrcHandler
|
||||
{
|
||||
public:
|
||||
explicit QrcHandler();
|
||||
static QString scheme();
|
||||
bool canHandleUrl(const QUrl &url) const;
|
||||
QByteArray dataForUrl(const QUrl &url) const;
|
||||
QString mimeTypeForUrl(const QUrl &url) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
10
client/core/webview/qrchandler_android.cpp
Normal file
10
client/core/webview/qrchandler_android.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include "qrchandler.h"
|
||||
#include "mimecache.h"
|
||||
|
||||
QrcHandler::QrcHandler()
|
||||
{}
|
||||
189
client/core/webview/qrchandler_ios.mm
Normal file
189
client/core/webview/qrchandler_ios.mm
Normal file
@@ -0,0 +1,189 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MobileCoreServices/UTCoreTypes.h>
|
||||
#import <MobileCoreServices/UTType.h>
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QDebug>
|
||||
#include "mimecache.h"
|
||||
|
||||
#include "qrchandler.h"
|
||||
#define kProtocolQrcScheme @"qrc"
|
||||
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
@interface QrcProtocol : NSURLProtocol
|
||||
|
||||
+ (NSString*)protocolScheme;
|
||||
+ (NSString*)requestVarsKey;
|
||||
+ (void)registerProtocol;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSURLRequest (QrcProtocol)
|
||||
- (NSDictionary *)requestVars;
|
||||
@end
|
||||
|
||||
@interface NSMutableURLRequest (QrcProtocol)
|
||||
- (void)setRequestVars:(NSDictionary *)vars;
|
||||
@end
|
||||
#endif
|
||||
|
||||
QrcHandler::QrcHandler()
|
||||
{
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
[QrcProtocol registerProtocol];
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(ENABLE_WKWEBVIEW)
|
||||
|
||||
@implementation NSURLRequest (QrcProtocol)
|
||||
|
||||
- (NSDictionary *)requestVars {
|
||||
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||
return [NSURLProtocol propertyForKey:[QrcProtocol requestVarsKey] inRequest:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSMutableURLRequest (QrcProtocol)
|
||||
|
||||
- (void)setRequestVars:(NSDictionary *)requestVars {
|
||||
|
||||
NSLog(@"%@ received %@", self, NSStringFromSelector(_cmd));
|
||||
|
||||
NSDictionary *specialVarsCopy = [requestVars copy];
|
||||
[NSURLProtocol setProperty:specialVarsCopy forKey:[QrcProtocol requestVarsKey] inRequest:self];
|
||||
[specialVarsCopy release];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation QrcProtocol
|
||||
|
||||
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||
{
|
||||
NSString *scheme = request.URL.scheme;
|
||||
if ([kProtocolQrcScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (NSString*) protocolScheme
|
||||
{
|
||||
return kProtocolQrcScheme;
|
||||
}
|
||||
|
||||
+ (NSString*) requestVarsKey {
|
||||
return @"requestVars";
|
||||
}
|
||||
|
||||
+ (void)registerProtocol
|
||||
{
|
||||
static bool qrcProtocolRegistered = false;
|
||||
if (!qrcProtocolRegistered) {
|
||||
[NSURLProtocol registerClass:[QrcProtocol class]];
|
||||
qrcProtocolRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString*)headFromHtml:(NSString*)html
|
||||
{
|
||||
if (!html) return nil;
|
||||
|
||||
NSString *head = nil;
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(?<=<head>)[\\w\\W.]*(?=</head>)" options:NSRegularExpressionCaseInsensitive error:nil];
|
||||
NSTextCheckingResult *headResult = [regex firstMatchInString:html options:0 range:NSMakeRange(0, html.length)];
|
||||
|
||||
if (headResult && headResult.range.location != NSNotFound) {
|
||||
head = [html substringWithRange:[headResult range]];
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
+ (NSString *)charsetFromHtml:(NSString *)html
|
||||
{
|
||||
if (!html) return nil;
|
||||
|
||||
NSString *charset = nil;
|
||||
NSString *charsetPattern = @"((?<=charset=)\\s*[a-zA-Z0-9-]*)";
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:charsetPattern options: NSRegularExpressionCaseInsensitive error:nil];
|
||||
NSTextCheckingResult *charsetResult = [regex firstMatchInString:html options:kNilOptions range:NSMakeRange(0, [html length])];
|
||||
if (charsetResult && charsetResult.range.location != NSNotFound) {
|
||||
charset = [[html substringWithRange:[charsetResult range]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
charset = [charset lowercaseString];
|
||||
}
|
||||
return charset;
|
||||
}
|
||||
|
||||
|
||||
- (void)startLoading
|
||||
{
|
||||
/* retrieve the current request. */
|
||||
NSURLRequest *request = [self request];
|
||||
NSString *url = [[request URL] path];
|
||||
//NSString *absoluteString = request.URL.absoluteString;
|
||||
|
||||
//NSString *mimeType = [self mimeTypeForExtension: [url pathExtension]];
|
||||
NSString *mimeType = mimeTypeForExtension(QString::fromNSString(url.pathExtension)).toNSString();
|
||||
|
||||
/* extract our special variables from the request. */
|
||||
//NSDictionary *requestVars = [request requestVars];
|
||||
NSData *pageData = nil;
|
||||
|
||||
QString requestUrl(QString(":/%1").arg(QString::fromNSString(url)));
|
||||
QFile resource(requestUrl);
|
||||
if (resource.exists() && resource.open(QIODevice::ReadOnly)) {
|
||||
|
||||
QByteArray buffer = resource.readAll();
|
||||
|
||||
//pageData = [[NSData alloc] initWithBytes:buffer.constData() length:buffer.size()];
|
||||
pageData = [NSData dataWithBytes:buffer.constData() length:buffer.size()];
|
||||
|
||||
resource.close();
|
||||
}
|
||||
|
||||
if (pageData) {
|
||||
|
||||
NSString *encoding = @"utf-8";
|
||||
if ([mimeType isEqualToString:@"text/html"]) {
|
||||
|
||||
NSString *content = [[NSString alloc] initWithBytesNoCopy: (char* )[pageData bytes] length:pageData.length encoding:NSISOLatin1StringEncoding freeWhenDone:NO];
|
||||
|
||||
NSString *charset = [QrcProtocol charsetFromHtml:[QrcProtocol headFromHtml:content]];
|
||||
if (charset) {
|
||||
encoding = charset;
|
||||
}
|
||||
[content autorelease];
|
||||
}
|
||||
|
||||
NSURLResponse *response = [[NSURLResponse alloc]initWithURL:self.request.URL
|
||||
MIMEType:mimeType
|
||||
expectedContentLength:[pageData length]
|
||||
textEncodingName:encoding];
|
||||
|
||||
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
|
||||
[[self client] URLProtocol:self didLoadData:pageData];
|
||||
[[self client] URLProtocolDidFinishLoading:self];
|
||||
[response autorelease];
|
||||
}
|
||||
else {
|
||||
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopLoading
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
202
client/core/webview/websettings.cpp
Normal file
202
client/core/webview/websettings.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QFont>
|
||||
#include <QGuiApplication>
|
||||
#include <QHash>
|
||||
#include <QSharedData>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrl>
|
||||
|
||||
#include "websettings.h"
|
||||
#include "amneziawebview_p.h"
|
||||
|
||||
AmneziaWebViewSettings::AmneziaWebViewSettings(AmneziaWebView *view): QObject(view), s(new WebSettings(view))
|
||||
{}
|
||||
|
||||
Q_GLOBAL_STATIC(QList<WebSettings*>, allSettings)
|
||||
|
||||
void WebSettings::apply()
|
||||
{
|
||||
if (view) {
|
||||
|
||||
WebSettings* global = WebSettings::globalSettings();
|
||||
|
||||
|
||||
QString family = fontFamilies.value(WebSettings::StandardFont,
|
||||
global->fontFamilies.value(WebSettings::StandardFont));
|
||||
view->setStandardFontFamily(family);
|
||||
|
||||
|
||||
int size = fontSizes.value(WebSettings::DefaultFontSize,
|
||||
global->fontSizes.value(WebSettings::DefaultFontSize));
|
||||
view->setDefaultFontSize(size);
|
||||
|
||||
bool value = attributes.value(WebSettings::AutoLoadImages,
|
||||
global->attributes.value(WebSettings::AutoLoadImages));
|
||||
|
||||
QString encoding = !defaultTextEncoding.isEmpty() ? defaultTextEncoding: global->defaultTextEncoding;
|
||||
|
||||
|
||||
Q_UNUSED(value)
|
||||
|
||||
} else {
|
||||
|
||||
QList<WebSettings*> settings = *::allSettings();
|
||||
for (int i = 0; i < settings.count(); ++i)
|
||||
settings[i]->apply();
|
||||
}
|
||||
}
|
||||
|
||||
WebSettings* WebSettings::globalSettings()
|
||||
{
|
||||
static WebSettings *global = nullptr;
|
||||
if (!global) {
|
||||
global = new WebSettings;
|
||||
}
|
||||
return global;
|
||||
}
|
||||
|
||||
WebSettings::WebSettings()
|
||||
{
|
||||
// Initialize our global defaults
|
||||
fontSizes.insert(WebSettings::MinimumFontSize, 0);
|
||||
fontSizes.insert(WebSettings::MinimumLogicalFontSize, 0);
|
||||
fontSizes.insert(WebSettings::DefaultFontSize, 16);
|
||||
fontSizes.insert(WebSettings::DefaultFixedFontSize, 13);
|
||||
|
||||
QFont defaultFont;
|
||||
defaultFont.setStyleHint(QFont::Serif);
|
||||
fontFamilies.insert(WebSettings::StandardFont, defaultFont.defaultFamily());
|
||||
fontFamilies.insert(WebSettings::SerifFont, defaultFont.defaultFamily());
|
||||
|
||||
defaultFont.setStyleHint(QFont::Fantasy);
|
||||
fontFamilies.insert(WebSettings::FantasyFont, defaultFont.defaultFamily());
|
||||
|
||||
defaultFont.setStyleHint(QFont::Cursive);
|
||||
fontFamilies.insert(WebSettings::CursiveFont, defaultFont.defaultFamily());
|
||||
|
||||
defaultFont.setStyleHint(QFont::SansSerif);
|
||||
fontFamilies.insert(WebSettings::SansSerifFont, defaultFont.defaultFamily());
|
||||
|
||||
defaultFont.setStyleHint(QFont::Monospace);
|
||||
fontFamilies.insert(WebSettings::FixedFont, defaultFont.defaultFamily());
|
||||
|
||||
attributes.insert(WebSettings::AutoLoadImages, true);
|
||||
attributes.insert(WebSettings::DnsPrefetchEnabled, false);
|
||||
attributes.insert(WebSettings::JavascriptEnabled, true);
|
||||
attributes.insert(WebSettings::SpatialNavigationEnabled, false);
|
||||
attributes.insert(WebSettings::LinksIncludedInFocusChain, true);
|
||||
attributes.insert(WebSettings::ZoomTextOnly, false);
|
||||
attributes.insert(WebSettings::PrintElementBackgrounds, true);
|
||||
attributes.insert(WebSettings::OfflineStorageDatabaseEnabled, false);
|
||||
attributes.insert(WebSettings::OfflineWebApplicationCacheEnabled, false);
|
||||
attributes.insert(WebSettings::LocalStorageEnabled, false);
|
||||
attributes.insert(WebSettings::LocalContentCanAccessRemoteUrls, false);
|
||||
attributes.insert(WebSettings::LocalContentCanAccessFileUrls, true);
|
||||
attributes.insert(WebSettings::AcceleratedCompositingEnabled, true);
|
||||
attributes.insert(WebSettings::WebGLEnabled, true);
|
||||
attributes.insert(WebSettings::WebAudioEnabled, false);
|
||||
attributes.insert(WebSettings::CSSRegionsEnabled, true);
|
||||
attributes.insert(WebSettings::CSSGridLayoutEnabled, false);
|
||||
attributes.insert(WebSettings::HyperlinkAuditingEnabled, false);
|
||||
attributes.insert(WebSettings::TiledBackingStoreEnabled, false);
|
||||
attributes.insert(WebSettings::FrameFlatteningEnabled, false);
|
||||
attributes.insert(WebSettings::SiteSpecificQuirksEnabled, true);
|
||||
attributes.insert(WebSettings::ScrollAnimatorEnabled, false);
|
||||
attributes.insert(WebSettings::CaretBrowsingEnabled, false);
|
||||
attributes.insert(WebSettings::NotificationsEnabled, true);
|
||||
|
||||
#if defined(Q_OS_WIN32) && defined(DEBUG)
|
||||
attributes.insert(WebSettings::DeveloperExtrasEnabled,true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
WebSettings::WebSettings(AmneziaWebView *v)
|
||||
: view(v)
|
||||
|
||||
{
|
||||
allSettings()->append(this);
|
||||
}
|
||||
|
||||
WebSettings::~WebSettings()
|
||||
{
|
||||
if (view)
|
||||
allSettings()->removeAll(this);
|
||||
|
||||
}
|
||||
|
||||
void WebSettings::setFontSize(FontSize type, int size)
|
||||
{
|
||||
fontSizes.insert(type, size);
|
||||
apply();
|
||||
}
|
||||
|
||||
int WebSettings::fontSize(FontSize type) const
|
||||
{
|
||||
int defaultValue = 0;
|
||||
if (view) {
|
||||
WebSettings* global = WebSettings::globalSettings();
|
||||
defaultValue = global->fontSizes.value(type);
|
||||
}
|
||||
return fontSizes.value(type, defaultValue);
|
||||
}
|
||||
|
||||
void WebSettings::resetFontSize(FontSize type)
|
||||
{
|
||||
if (view) {
|
||||
fontSizes.remove(type);
|
||||
apply();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSettings::setFontFamily(FontFamily which, const QString& family)
|
||||
{
|
||||
fontFamilies.insert(which, family);
|
||||
apply();
|
||||
}
|
||||
|
||||
QString WebSettings::fontFamily(FontFamily which) const
|
||||
{
|
||||
QString defaultValue;
|
||||
if (view) {
|
||||
WebSettings* global = WebSettings::globalSettings();
|
||||
defaultValue = global->fontFamilies.value(which);
|
||||
}
|
||||
return fontFamilies.value(which, defaultValue);
|
||||
}
|
||||
|
||||
void WebSettings::resetFontFamily(FontFamily which)
|
||||
{
|
||||
if (view) {
|
||||
fontFamilies.remove(which);
|
||||
apply();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSettings::setAttribute(WebAttribute attr, bool on)
|
||||
{
|
||||
attributes.insert(attr, on);
|
||||
apply();
|
||||
}
|
||||
|
||||
bool WebSettings::testAttribute(WebAttribute attr) const
|
||||
{
|
||||
bool defaultValue = false;
|
||||
if (view) {
|
||||
WebSettings* global = WebSettings::globalSettings();
|
||||
defaultValue = global->attributes.value(attr);
|
||||
}
|
||||
return attributes.value(attr, defaultValue);
|
||||
}
|
||||
|
||||
void WebSettings::resetAttribute(WebAttribute attr)
|
||||
{
|
||||
if (view) {
|
||||
attributes.remove(attr);
|
||||
apply();
|
||||
}
|
||||
}
|
||||
144
client/core/webview/websettings.h
Normal file
144
client/core/webview/websettings.h
Normal file
@@ -0,0 +1,144 @@
|
||||
#ifndef WEBSETTINGS_H
|
||||
#define WEBSETTINGS_H
|
||||
|
||||
#include <QtQml>
|
||||
|
||||
class AmneziaWebView;
|
||||
class AmneziaWebViewPrivate;
|
||||
class WebSettingsData;
|
||||
|
||||
class WebSettings
|
||||
{
|
||||
public:
|
||||
enum FontFamily {
|
||||
StandardFont,
|
||||
FixedFont,
|
||||
SerifFont,
|
||||
SansSerifFont,
|
||||
CursiveFont,
|
||||
FantasyFont
|
||||
};
|
||||
enum WebAttribute {
|
||||
AutoLoadImages,
|
||||
JavascriptEnabled,
|
||||
JavaEnabled,
|
||||
PluginsEnabled,
|
||||
PrivateBrowsingEnabled,
|
||||
JavascriptCanOpenWindows,
|
||||
JavascriptCanAccessClipboard,
|
||||
DeveloperExtrasEnabled,
|
||||
LinksIncludedInFocusChain,
|
||||
ZoomTextOnly,
|
||||
PrintElementBackgrounds,
|
||||
OfflineStorageDatabaseEnabled,
|
||||
OfflineWebApplicationCacheEnabled,
|
||||
LocalStorageEnabled,
|
||||
LocalContentCanAccessRemoteUrls,
|
||||
DnsPrefetchEnabled,
|
||||
XSSAuditingEnabled,
|
||||
AcceleratedCompositingEnabled,
|
||||
SpatialNavigationEnabled,
|
||||
LocalContentCanAccessFileUrls,
|
||||
TiledBackingStoreEnabled,
|
||||
FrameFlatteningEnabled,
|
||||
SiteSpecificQuirksEnabled,
|
||||
JavascriptCanCloseWindows,
|
||||
WebGLEnabled,
|
||||
CSSRegionsEnabled,
|
||||
HyperlinkAuditingEnabled,
|
||||
CSSGridLayoutEnabled,
|
||||
ScrollAnimatorEnabled,
|
||||
CaretBrowsingEnabled,
|
||||
NotificationsEnabled,
|
||||
WebAudioEnabled
|
||||
};
|
||||
enum WebGraphic {
|
||||
MissingImageGraphic,
|
||||
MissingPluginGraphic,
|
||||
DefaultFrameIconGraphic,
|
||||
TextAreaSizeGripCornerGraphic,
|
||||
DeleteButtonGraphic,
|
||||
InputSpeechButtonGraphic,
|
||||
SearchCancelButtonGraphic,
|
||||
SearchCancelButtonPressedGraphic
|
||||
};
|
||||
enum FontSize {
|
||||
MinimumFontSize,
|
||||
MinimumLogicalFontSize,
|
||||
DefaultFontSize,
|
||||
DefaultFixedFontSize
|
||||
};
|
||||
enum ThirdPartyCookiePolicy {
|
||||
AlwaysAllowThirdPartyCookies,
|
||||
AlwaysBlockThirdPartyCookies,
|
||||
AllowThirdPartyWithExistingCookies
|
||||
};
|
||||
|
||||
static WebSettings *globalSettings();
|
||||
|
||||
void setFontSize(FontSize type, int size);
|
||||
int fontSize(FontSize type) const;
|
||||
void resetFontSize(FontSize type);
|
||||
|
||||
|
||||
void setFontFamily(FontFamily which, const QString &family);
|
||||
QString fontFamily(FontFamily which) const;
|
||||
void resetFontFamily(FontFamily which);
|
||||
|
||||
void setAttribute(WebAttribute attr, bool on);
|
||||
bool testAttribute(WebAttribute attr) const;
|
||||
void resetAttribute(WebAttribute attr);
|
||||
|
||||
void apply();
|
||||
|
||||
WebSettings();
|
||||
explicit WebSettings(AmneziaWebView *v);
|
||||
virtual ~WebSettings();
|
||||
|
||||
private:
|
||||
friend class WebSettingsData;
|
||||
friend class AmneziaWebViewPrivate;
|
||||
friend class WebViewPrivate;
|
||||
|
||||
|
||||
|
||||
Q_DISABLE_COPY(WebSettings)
|
||||
|
||||
QHash<int, QString> fontFamilies;
|
||||
QHash<int, int> fontSizes;
|
||||
QHash<int, bool> attributes;
|
||||
QString defaultTextEncoding;
|
||||
AmneziaWebView *view;
|
||||
|
||||
};
|
||||
|
||||
class AmneziaWebViewSettings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(int defaultFontSize READ defaultFontSize WRITE setDefaultFontSize)
|
||||
Q_PROPERTY(QString standardFontFamily READ standardFontFamily WRITE setStandardFontFamily)
|
||||
Q_PROPERTY(bool developerExtrasEnabled READ developerExtrasEnabled WRITE setDeveloperExtrasEnabled)
|
||||
|
||||
public:
|
||||
explicit AmneziaWebViewSettings(AmneziaWebView *parent);
|
||||
|
||||
int defaultFontSize() const { return s->fontSize(WebSettings::DefaultFontSize); }
|
||||
void setDefaultFontSize(int size) { s->setFontSize(WebSettings::DefaultFontSize, size); }
|
||||
|
||||
QString standardFontFamily() const { return s->fontFamily(WebSettings::StandardFont); }
|
||||
void setStandardFontFamily(const QString& f) { s->setFontFamily(WebSettings::StandardFont, f); }
|
||||
|
||||
bool developerExtrasEnabled() const { return s->testAttribute(WebSettings::DeveloperExtrasEnabled); }
|
||||
void setDeveloperExtrasEnabled(bool on) { s->setAttribute(WebSettings::DeveloperExtrasEnabled, on); }
|
||||
|
||||
void apply() { s->apply(); }
|
||||
|
||||
private:
|
||||
QScopedPointer<WebSettings> s;
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(AmneziaWebViewSettings)
|
||||
|
||||
|
||||
#endif
|
||||
3
client/core/webview/webview.json
Normal file
3
client/core/webview/webview.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": [ "AmneziaWebView" ]
|
||||
}
|
||||
@@ -149,8 +149,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
||||
// set routing
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.debug() << "Routing configuration failed for"
|
||||
<< logger.sensitive(ip.toString());
|
||||
logger.debug() << "Routing configuration failed for" << ip.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -170,11 +169,14 @@ bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||
QList<QHostAddress> resolvers;
|
||||
resolvers.append(QHostAddress(config.m_dnsServer));
|
||||
resolvers.append(QHostAddress(config.m_primaryDnsServer));
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
|
||||
}
|
||||
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
|
||||
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||
}
|
||||
|
||||
@@ -280,15 +282,26 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
|
||||
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
|
||||
|
||||
if (!obj.contains("dnsServer")) {
|
||||
config.m_dnsServer = QString();
|
||||
if (!obj.contains("primaryDnsServer")) {
|
||||
config.m_primaryDnsServer = QString();
|
||||
} else {
|
||||
QJsonValue value = obj.value("dnsServer");
|
||||
QJsonValue value = obj.value("primaryDnsServer");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "dnsServer is not a string";
|
||||
return false;
|
||||
}
|
||||
config.m_dnsServer = value.toString();
|
||||
config.m_primaryDnsServer = value.toString();
|
||||
}
|
||||
|
||||
if (!obj.contains("secondaryDnsServer")) {
|
||||
config.m_secondaryDnsServer = QString();
|
||||
} else {
|
||||
QJsonValue value = obj.value("secondaryDnsServer");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "dnsServer is not a string";
|
||||
return false;
|
||||
}
|
||||
config.m_secondaryDnsServer = value.toString();
|
||||
}
|
||||
|
||||
if (!obj.contains("hopType")) {
|
||||
@@ -371,6 +384,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
@@ -389,6 +405,13 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
if (!obj.value("S2").isNull()) {
|
||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||
}
|
||||
if (!obj.value("S3").isNull()) {
|
||||
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
|
||||
}
|
||||
if (!obj.value("S4").isNull()) {
|
||||
config.m_transportPacketJunkSize = obj.value("S4").toString();
|
||||
}
|
||||
|
||||
if (!obj.value("H1").isNull()) {
|
||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||
}
|
||||
@@ -402,6 +425,22 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||
}
|
||||
|
||||
if (!obj.value("I1").isNull()) {
|
||||
config.m_specialJunk["I1"] = obj.value("I1").toString();
|
||||
}
|
||||
if (!obj.value("I2").isNull()) {
|
||||
config.m_specialJunk["I2"] = obj.value("I2").toString();
|
||||
}
|
||||
if (!obj.value("I3").isNull()) {
|
||||
config.m_specialJunk["I3"] = obj.value("I3").toString();
|
||||
}
|
||||
if (!obj.value("I4").isNull()) {
|
||||
config.m_specialJunk["I4"] = obj.value("I4").toString();
|
||||
}
|
||||
if (!obj.value("I5").isNull()) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -444,7 +483,7 @@ bool Daemon::deactivate(bool emitSignals) {
|
||||
|
||||
m_connections.clear();
|
||||
// Delete the interface
|
||||
return wgutils()->deleteInterface();
|
||||
return wgutils()->deleteInterface();
|
||||
}
|
||||
|
||||
QString Daemon::logs() {
|
||||
|
||||
@@ -28,7 +28,8 @@ QJsonObject InterfaceConfig::toJson() const {
|
||||
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
|
||||
json.insert("dnsServer", QJsonValue(m_dnsServer));
|
||||
json.insert("primaryDnsServer", QJsonValue(m_primaryDnsServer));
|
||||
json.insert("secondaryDnsServer", QJsonValue(m_secondaryDnsServer));
|
||||
}
|
||||
|
||||
QJsonArray allowedIPAddesses;
|
||||
@@ -48,6 +49,13 @@ QJsonObject InterfaceConfig::toJson() const {
|
||||
}
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
|
||||
QJsonArray jsAllowedDnsServers;
|
||||
for (const QString& i : m_allowedDnsServers) {
|
||||
jsAllowedDnsServers.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("allowedDnsServers", jsAllowedDnsServers);
|
||||
|
||||
QJsonArray disabledApps;
|
||||
for (const QString& i : m_vpnDisabledApps) {
|
||||
disabledApps.append(QJsonValue(i));
|
||||
@@ -93,11 +101,15 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
out << "MTU = " << m_deviceMTU << "\n";
|
||||
}
|
||||
|
||||
if (!m_dnsServer.isNull()) {
|
||||
QStringList dnsServers(m_dnsServer);
|
||||
if (!m_primaryDnsServer.isEmpty()) {
|
||||
QStringList dnsServers;
|
||||
dnsServers.append(m_primaryDnsServer);
|
||||
if (!m_secondaryDnsServer.isEmpty()) {
|
||||
dnsServers.append(m_secondaryDnsServer);
|
||||
}
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (m_dnsServer == m_serverIpv4Gateway) {
|
||||
if (m_primaryDnsServer == m_serverIpv4Gateway) {
|
||||
dnsServers.append(m_serverIpv6Gateway);
|
||||
}
|
||||
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||
@@ -118,6 +130,12 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
if (!m_responsePacketJunkSize.isNull()) {
|
||||
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_cookieReplyPacketJunkSize.isNull()) {
|
||||
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_transportPacketJunkSize.isNull()) {
|
||||
out << "S4 = " << m_transportPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_initPacketMagicHeader.isNull()) {
|
||||
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
@@ -131,6 +149,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
for (const QString& key : m_specialJunk.keys()) {
|
||||
out << key << " = " << m_specialJunk[key] << "\n";
|
||||
}
|
||||
|
||||
// If any extra config was provided, append it now.
|
||||
for (const QString& key : extra.keys()) {
|
||||
out << key << " = " << extra[key] << "\n";
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
#define INTERFACECONFIG_H
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <QMap>
|
||||
#include "ipaddress.h"
|
||||
|
||||
class QJsonObject;
|
||||
@@ -31,12 +32,14 @@ class InterfaceConfig {
|
||||
QString m_serverIpv4AddrIn;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv6AddrIn;
|
||||
QString m_dnsServer;
|
||||
QString m_primaryDnsServer;
|
||||
QString m_secondaryDnsServer;
|
||||
int m_serverPort = 0;
|
||||
int m_deviceMTU = 1420;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
QStringList m_allowedDnsServers;
|
||||
bool m_killSwitchEnabled;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
@@ -47,10 +50,13 @@ class InterfaceConfig {
|
||||
QString m_junkPacketMaxSize;
|
||||
QString m_initPacketJunkSize;
|
||||
QString m_responsePacketJunkSize;
|
||||
QString m_cookieReplyPacketJunkSize;
|
||||
QString m_transportPacketJunkSize;
|
||||
QString m_initPacketMagicHeader;
|
||||
QString m_responsePacketMagicHeader;
|
||||
QString m_underloadPacketMagicHeader;
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
|
||||
14
client/images/controls/news-unread.svg
Normal file
14
client/images/controls/news-unread.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4_34)">
|
||||
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4_34">
|
||||
<rect width="74" height="74" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 982 B |
8
client/images/controls/news.svg
Normal file
8
client/images/controls/news.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Основа газеты -->
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||
<!-- Линии текста -->
|
||||
<line x1="7" y1="8" x2="17" y2="8"/>
|
||||
<line x1="7" y1="12" x2="17" y2="12"/>
|
||||
<line x1="7" y1="16" x2="13" y2="16"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 410 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user