mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-10-31 00:57:57 +09:00
Compare commits
112
Commits
| 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 |
No files matched your search
+119
-32
@@ -13,7 +13,7 @@ jobs:
|
||||
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 }}
|
||||
@@ -44,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
|
||||
|
||||
@@ -55,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'
|
||||
@@ -84,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 }}
|
||||
@@ -102,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
|
||||
|
||||
@@ -124,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'
|
||||
@@ -149,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 }}
|
||||
@@ -167,7 +210,7 @@ jobs:
|
||||
- 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
|
||||
@@ -331,7 +374,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.8.0
|
||||
QT_VERSION: 6.9.2
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
@@ -361,7 +404,7 @@ jobs:
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
@@ -369,10 +412,15 @@ jobs:
|
||||
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'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
cache: 'true'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
@@ -380,6 +428,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
|
||||
|
||||
@@ -388,11 +443,17 @@ jobs:
|
||||
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_MacOS_installer
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
@@ -406,7 +467,7 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.8.3
|
||||
QT_VERSION: 6.9.2
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
@@ -426,21 +487,31 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.2.0'
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install Qt'
|
||||
- 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'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
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
|
||||
@@ -469,8 +540,8 @@ 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 }}
|
||||
@@ -551,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
|
||||
|
||||
@@ -584,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
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
|
||||
# Qt-es
|
||||
|
||||
+35
-2
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.8.9.2)
|
||||
set(AMNEZIAVPN_VERSION 4.8.12.5)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
@@ -12,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 2092)
|
||||
set(APP_ANDROID_VERSION_CODE 2101)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -49,3 +49,36 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
|
||||
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()
|
||||
+1
-1
Submodule client/3rd-prebuilt updated: 02a00ee00d...adf5eb920f.
Vendored
+1
-1
Submodule client/3rd/amneziawg-apple updated: 811af0a83b...cf63135331.
@@ -56,7 +56,7 @@ 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)
|
||||
@@ -105,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
|
||||
@@ -194,7 +197,7 @@ elseif(APPLE)
|
||||
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
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
#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"
|
||||
@@ -25,8 +32,15 @@
|
||||
#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
|
||||
@@ -51,11 +65,13 @@ 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);
|
||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
|
||||
}
|
||||
#endif
|
||||
|
||||
m_vpnConnectionThread.requestInterruption();
|
||||
m_vpnConnectionThread.quit();
|
||||
@@ -71,10 +87,31 @@ 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,
|
||||
@@ -106,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();
|
||||
@@ -121,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();
|
||||
@@ -145,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()
|
||||
@@ -189,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();
|
||||
@@ -230,8 +289,12 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
quit();
|
||||
#else
|
||||
if (m_coreController && m_coreController->pageController()) {
|
||||
m_coreController->pageController()->hideMainWindow();
|
||||
if (m_forceQuit) {
|
||||
quit();
|
||||
} else {
|
||||
if (m_coreController && m_coreController->pageController()) {
|
||||
m_coreController->pageController()->hideMainWindow();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true; // eat the close
|
||||
@@ -240,6 +303,12 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void AmneziaApplication::forceQuit()
|
||||
{
|
||||
m_forceQuit = true;
|
||||
quit();
|
||||
}
|
||||
|
||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||
{
|
||||
return m_engine;
|
||||
|
||||
@@ -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,6 +60,11 @@ private:
|
||||
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QCommandLineOption m_optAutostart;
|
||||
QCommandLineOption m_optCleanup;
|
||||
QCommandLineOption m_optConnect;
|
||||
QCommandLineOption m_optImport;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+29
-17
@@ -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 {
|
||||
@@ -122,23 +125,24 @@ open class Wireguard : Protocol() {
|
||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
||||
configData.optStringOrNull("S4")?.let { setS4(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("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) }
|
||||
configData.optStringOrNull("J1")?.let { setJ1(it) }
|
||||
configData.optStringOrNull("J2")?.let { setJ2(it) }
|
||||
configData.optStringOrNull("J3")?.let { setJ3(it) }
|
||||
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -146,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")
|
||||
}
|
||||
@@ -202,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)
|
||||
}
|
||||
}
|
||||
+12
-32
@@ -22,19 +22,15 @@ open class WireguardConfig protected constructor(
|
||||
val s2: Int?,
|
||||
val s3: Int?,
|
||||
val s4: Int?,
|
||||
val h1: Long?,
|
||||
val h2: Long?,
|
||||
val h3: Long?,
|
||||
val h4: Long?,
|
||||
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?,
|
||||
var j1: String?,
|
||||
var j2: String?,
|
||||
var j3: String?,
|
||||
var itime: Int?
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -61,10 +57,6 @@ open class WireguardConfig protected constructor(
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
builder.j1,
|
||||
builder.j2,
|
||||
builder.j3,
|
||||
builder.itime
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -94,10 +86,6 @@ open class WireguardConfig protected constructor(
|
||||
i3?.let { appendLine("i3=$it") }
|
||||
i4?.let { appendLine("i4=$it") }
|
||||
i5?.let { appendLine("i5=$it") }
|
||||
j1?.let { appendLine("j1=$it") }
|
||||
j2?.let { appendLine("j2=$it") }
|
||||
j3?.let { appendLine("j3=$it") }
|
||||
itime?.let { appendLine("itime=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,19 +140,15 @@ open class WireguardConfig protected constructor(
|
||||
internal var s2: Int? = null
|
||||
internal var s3: Int? = null
|
||||
internal var s4: Int? = null
|
||||
internal var h1: Long? = null
|
||||
internal var h2: Long? = null
|
||||
internal var h3: Long? = null
|
||||
internal var h4: Long? = 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
|
||||
internal var j1: String? = null
|
||||
internal var j2: String? = null
|
||||
internal var j3: String? = null
|
||||
internal var itime: Int? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
@@ -185,19 +169,15 @@ open class WireguardConfig protected constructor(
|
||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
||||
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||
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 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 }
|
||||
fun setJ1(j1: String) = apply { this.j1 = j1 }
|
||||
fun setJ2(j2: String) = apply { this.j2 = j2 }
|
||||
fun setJ3(j3: String) = apply { this.j3 = j3 }
|
||||
fun setItime(itime: Int) = apply { this.itime = itime }
|
||||
|
||||
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")
|
||||
|
||||
@@ -38,7 +38,7 @@ elseif(APPLE AND NOT IOS)
|
||||
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")
|
||||
@@ -62,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
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ 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
|
||||
)
|
||||
@@ -45,6 +46,7 @@ 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
|
||||
|
||||
@@ -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,7 +37,6 @@ 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 AND NOT MACOS_NE)
|
||||
@@ -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,7 +87,6 @@ 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 AND NOT MACOS_NE)
|
||||
@@ -175,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)
|
||||
|
||||
@@ -189,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
|
||||
@@ -203,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()
|
||||
@@ -41,18 +41,16 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||
|
||||
// jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
|
||||
// jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
|
||||
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::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
|
||||
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
|
||||
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
|
||||
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
|
||||
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);
|
||||
|
||||
@@ -83,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>", "");
|
||||
@@ -117,7 +135,7 @@ 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);
|
||||
|
||||
@@ -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,
|
||||
@@ -182,7 +193,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||
"* Minimal configuration required\n"
|
||||
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Awg,
|
||||
{ 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, "
|
||||
@@ -242,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;
|
||||
@@ -255,6 +267,15 @@ 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
|
||||
@@ -265,6 +286,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
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;
|
||||
@@ -278,6 +300,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// 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;
|
||||
@@ -300,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;
|
||||
@@ -329,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;
|
||||
}
|
||||
}
|
||||
@@ -337,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 "";
|
||||
}
|
||||
}
|
||||
@@ -345,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 "";
|
||||
}
|
||||
@@ -354,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;
|
||||
}
|
||||
}
|
||||
@@ -370,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))
|
||||
@@ -387,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);
|
||||
|
||||
@@ -47,12 +47,14 @@ 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");
|
||||
@@ -64,6 +66,17 @@ namespace apiDefs
|
||||
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,6 +1,7 @@
|
||||
#include "apiUtils.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace
|
||||
@@ -23,7 +24,7 @@ namespace
|
||||
|
||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||
return endDate < now;
|
||||
}
|
||||
@@ -82,34 +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) {
|
||||
qDebug() << reply->error();
|
||||
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << replyError;
|
||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
qDebug() << reply->error();
|
||||
} 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;
|
||||
}
|
||||
@@ -162,3 +174,51 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -18,9 +18,12 @@ namespace apiUtils
|
||||
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
|
||||
@@ -26,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()
|
||||
@@ -100,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()
|
||||
@@ -154,6 +156,9 @@ void CoreController::initControllers()
|
||||
|
||||
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()
|
||||
@@ -233,7 +238,7 @@ void CoreController::initSignalHandlers()
|
||||
|
||||
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(),
|
||||
@@ -317,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();
|
||||
}
|
||||
|
||||
@@ -401,3 +411,22 @@ 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,13 +5,14 @@
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#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"
|
||||
@@ -47,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
|
||||
|
||||
@@ -63,6 +65,9 @@ public:
|
||||
QSharedPointer<PageController> pageController() const;
|
||||
void setQmlRoot();
|
||||
|
||||
void openConnectionByIndex(int serverIndex);
|
||||
void importConfigFromData(const QString &data);
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
void websiteUrlChanged(const QString &newUrl);
|
||||
@@ -97,7 +102,7 @@ private:
|
||||
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
|
||||
|
||||
@@ -118,6 +123,7 @@ private:
|
||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||
QScopedPointer<ApiNewsController> m_apiNewsController;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
@@ -125,6 +131,7 @@ 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,12 +1,15 @@
|
||||
#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"
|
||||
@@ -38,6 +41,11 @@ namespace
|
||||
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, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
@@ -50,101 +58,45 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
||||
{
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||
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));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString host = QUrl(encRequestData.request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
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";
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
@@ -159,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);
|
||||
@@ -233,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();
|
||||
@@ -286,7 +395,10 @@ QStringList GatewayController::getProxyUrls()
|
||||
}
|
||||
return endpoints;
|
||||
} else {
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
auto replyError = reply->error();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << replyError;
|
||||
qDebug() << httpStatusCode;
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
|
||||
reply->deleteLater();
|
||||
@@ -295,70 +407,259 @@ QStringList GatewayController::getProxyUrls()
|
||||
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) {
|
||||
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() << reply->error();
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
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() << reply->error();
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
} else if (httpStatus == httpStatusCodeNotImplemented) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << reply->error();
|
||||
qDebug() << replyError;
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << reply->error();
|
||||
} 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) {
|
||||
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";
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
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"
|
||||
|
||||
@@ -18,20 +22,49 @@ public:
|
||||
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
|
||||
@@ -345,7 +345,7 @@ 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)
|
||||
@@ -367,11 +367,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
|| (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::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))
|
||||
!= 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;
|
||||
}
|
||||
@@ -648,6 +648,11 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
|
||||
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) } });
|
||||
@@ -657,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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,8 @@ namespace amnezia
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
ApiSubscriptionExpiredError = 1112,
|
||||
ApiPurchaseError = 1113,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -77,6 +77,8 @@ QString errorString(ErrorCode code) {
|
||||
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;
|
||||
|
||||
+47
-80
@@ -1,109 +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";
|
||||
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||
|
||||
return Instance()->isSocketConnected();
|
||||
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()));
|
||||
|
||||
@@ -111,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) {
|
||||
@@ -125,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;
|
||||
|
||||
+36
-21
@@ -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,11 +1,12 @@
|
||||
#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>
|
||||
@@ -30,6 +31,15 @@
|
||||
#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>
|
||||
@@ -170,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;
|
||||
@@ -239,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 =
|
||||
@@ -252,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||
@@ -267,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,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;
|
||||
@@ -292,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));
|
||||
@@ -316,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
/* send msg */
|
||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||
perror("send failed");
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
/* receive response */
|
||||
@@ -325,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;
|
||||
@@ -335,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return "";
|
||||
return {};
|
||||
}
|
||||
|
||||
/* If we received all data break */
|
||||
@@ -388,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) && !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};
|
||||
|
||||
@@ -401,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;
|
||||
@@ -449,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;
|
||||
}
|
||||
}
|
||||
@@ -463,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;
|
||||
}
|
||||
}
|
||||
@@ -472,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
|
||||
{
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
static bool checkIPv4Format(const QString &ip);
|
||||
static bool checkIpSubnetFormat(const QString &ip);
|
||||
static bool checkIpv6Enabled();
|
||||
static QString getGatewayAndIface();
|
||||
static QPair<QString, QNetworkInterface> getGatewayAndIface();
|
||||
// Returns the Interface Index that could Route to dst
|
||||
static int AdapterIndexTo(const QHostAddress& dst);
|
||||
|
||||
|
||||
@@ -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(); });
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include "filehandler.h"
|
||||
|
||||
FileHandler::FileHandler()
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
module AmneziaWebView
|
||||
plugin webview
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
#include "qrchandler.h"
|
||||
#include "mimecache.h"
|
||||
|
||||
QrcHandler::QrcHandler()
|
||||
{}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": [ "AmneziaWebView" ]
|
||||
}
|
||||
@@ -440,18 +440,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
if (!obj.value("I5").isNull()) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
if (!obj.value("J1").isNull()) {
|
||||
config.m_controlledJunk["J1"] = obj.value("J1").toString();
|
||||
}
|
||||
if (!obj.value("J2").isNull()) {
|
||||
config.m_controlledJunk["J2"] = obj.value("J2").toString();
|
||||
}
|
||||
if (!obj.value("J3").isNull()) {
|
||||
config.m_controlledJunk["J3"] = obj.value("J3").toString();
|
||||
}
|
||||
if (!obj.value("Itime").isNull()) {
|
||||
config.m_specialHandshakeTimeout = obj.value("Itime").toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
out << "MTU = " << m_deviceMTU << "\n";
|
||||
}
|
||||
|
||||
if (!m_primaryDnsServer.isNull()) {
|
||||
if (!m_primaryDnsServer.isEmpty()) {
|
||||
QStringList dnsServers;
|
||||
dnsServers.append(m_primaryDnsServer);
|
||||
if (!m_secondaryDnsServer.isNull()) {
|
||||
if (!m_secondaryDnsServer.isEmpty()) {
|
||||
dnsServers.append(m_secondaryDnsServer);
|
||||
}
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
@@ -152,12 +152,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
for (const QString& key : m_specialJunk.keys()) {
|
||||
out << key << " = " << m_specialJunk[key] << "\n";
|
||||
}
|
||||
for (const QString& key : m_controlledJunk.keys()) {
|
||||
out << key << " = " << m_controlledJunk[key] << "\n";
|
||||
}
|
||||
if (!m_specialHandshakeTimeout.isNull()) {
|
||||
out << "Itime = " << m_specialHandshakeTimeout << "\n";
|
||||
}
|
||||
|
||||
// If any extra config was provided, append it now.
|
||||
for (const QString& key : extra.keys()) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <QMap>
|
||||
#include "ipaddress.h"
|
||||
|
||||
class QJsonObject;
|
||||
@@ -57,8 +57,6 @@ class InterfaceConfig {
|
||||
QString m_underloadPacketMagicHeader;
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
QMap<QString, QString> m_controlledJunk;
|
||||
QString m_specialHandshakeTimeout;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.0001 12C14.0001 10.8954 13.1047 10 12.0001 10C10.8955 10 10.0001 10.8954 10.0001 12C10.0001 13.1046 10.8955 14 12.0001 14C13.1047 14 14.0001 13.1046 14.0001 12ZM16.0001 12C16.0001 14.2091 14.2092 16 12.0001 16C9.79094 16 8.00008 14.2091 8.00008 12C8.00008 9.79086 9.79094 8 12.0001 8C14.2092 8 16.0001 9.79086 16.0001 12Z" fill="#D7D8DB"/>
|
||||
<path d="M20.5001 6C21.8808 6 23.0001 4.88071 23.0001 3.5C23.0001 2.11929 21.8808 1 20.5001 1C19.1194 1 18.0001 2.11929 18.0001 3.5C18.0001 4.88071 19.1194 6 20.5001 6Z" fill="#FBB36A"/>
|
||||
<path d="M12.2198 1C12.9657 1 13.6811 1.28098 14.2315 1.7793C14.0814 2.32745 14.0001 2.90421 14.0001 3.5C14.0001 4.75704 14.3578 5.93021 14.9757 6.9248L14.7198 6.77539C14.2644 6.51244 13.8864 6.13489 13.6231 5.67969C13.3599 5.22423 13.2203 4.70672 13.2198 4.18066V4C13.2198 3.73478 13.1144 3.48051 12.9268 3.29297C12.7393 3.10557 12.4849 3 12.2198 3H11.7803C11.5153 3.00005 11.2608 3.10556 11.0733 3.29297C10.8858 3.4805 10.7803 3.73482 10.7803 4V4.18066C10.7798 4.70675 10.6403 5.22422 10.377 5.67969C10.1138 6.1349 9.73575 6.51343 9.28035 6.77637L9.27937 6.77539L8.85261 7.02441L8.84968 7.02637C8.39376 7.28946 7.87607 7.42773 7.34968 7.42773C6.82327 7.42764 6.30558 7.28958 5.84968 7.02637L5.72956 6.96191L5.70027 6.94629C5.47084 6.81401 5.1983 6.77827 4.94245 6.84668C4.71846 6.90662 4.52295 7.04188 4.38874 7.22852L4.33503 7.31152L4.11531 7.69141C3.98385 7.92039 3.94855 8.19212 4.01667 8.44727C4.07655 8.6712 4.21199 8.86674 4.39851 9.00098L4.48152 9.05469L4.53523 9.08789L4.68464 9.1875C5.11379 9.44747 5.47116 9.8098 5.7237 10.2441C5.98685 10.6968 6.12679 11.2108 6.12995 11.7344V12.2461L6.1241 12.4434C6.09561 12.9034 5.96204 13.3518 5.73152 13.7529C5.47084 14.2065 5.09507 14.5827 4.64363 14.8467L4.6446 14.8477L4.49421 14.9375L4.48152 14.9453C4.25235 15.0781 4.08509 15.2969 4.01667 15.5527C3.94855 15.8079 3.98385 16.0796 4.11531 16.3086L4.33406 16.6885C4.4668 16.9177 4.68662 17.0849 4.94245 17.1533C5.1983 17.2217 5.47084 17.186 5.70027 17.0537L5.72956 17.0381L5.84968 16.9736L6.02351 16.8809C6.43513 16.678 6.88902 16.5724 7.34968 16.5723C7.81025 16.5723 8.26427 16.6781 8.67585 16.8809L8.84968 16.9736L8.85261 16.9756L9.27937 17.2236C9.73477 17.4866 10.1138 17.8651 10.377 18.3203C10.6403 18.7758 10.7798 19.2932 10.7803 19.8193V20C10.7803 20.2652 10.8858 20.5195 11.0733 20.707C11.2608 20.8944 11.5153 21 11.7803 21H12.2198C12.4849 21 12.7393 20.8944 12.9268 20.707C13.1144 20.5195 13.2198 20.2652 13.2198 20V19.8193L13.2266 19.6221C13.2572 19.1646 13.3928 18.7188 13.6231 18.3203C13.8534 17.9221 14.1718 17.5828 14.5528 17.3281L14.7198 17.2236L15.1475 16.9756L15.1505 16.9736L15.3243 16.8809C15.7359 16.6781 16.1899 16.5723 16.6505 16.5723C17.1769 16.5723 17.6946 16.7104 18.1505 16.9736L18.2999 17.0537C18.5293 17.186 18.8018 17.2217 19.0577 17.1533C19.3136 17.0849 19.5323 16.9177 19.6651 16.6885L19.879 16.3086L19.8839 16.3008L19.9288 16.2119C20.0233 16.0026 20.0433 15.7658 19.9835 15.542C19.915 15.2863 19.7477 15.0683 19.5186 14.9355L19.3995 14.8721C19.3898 14.8669 19.3797 14.862 19.3702 14.8564C18.9124 14.5921 18.532 14.2112 18.2686 13.7529C18.0053 13.2946 17.8681 12.7746 17.8702 12.2461V11.75C17.8688 11.2228 18.0059 10.7042 18.2686 10.2471C18.3758 10.0606 18.5033 9.88765 18.6466 9.72949C19.2341 9.90406 19.8558 10 20.5001 10C20.8249 10 21.144 9.97473 21.4561 9.92871C21.2126 10.2693 20.9013 10.5607 20.5333 10.7764L20.5343 10.7773L20.3702 10.876C20.2176 10.9641 20.0908 11.0914 20.003 11.2441C19.9153 11.3968 19.8695 11.57 19.8702 11.7461V12.2539C19.8695 12.43 19.9153 12.6032 20.003 12.7559C20.0894 12.9062 20.214 13.0312 20.3634 13.1191L20.4903 13.1875L20.5216 13.2051L20.7706 13.3662C21.3298 13.7689 21.7365 14.3539 21.9161 15.0254C22.1205 15.7898 22.0131 16.6035 21.6202 17.29L21.6212 17.291L21.4014 17.6816L21.3956 17.6914C20.9972 18.379 20.342 18.8807 19.5743 19.0859C18.817 19.2883 18.0116 19.1847 17.3292 18.8008V18.8027L17.1798 18.7227C17.17 18.7175 17.16 18.7116 17.1505 18.7061C16.9986 18.6184 16.8258 18.5723 16.6505 18.5723C16.4751 18.5723 16.3024 18.6184 16.1505 18.7061L16.1495 18.7051L15.7227 18.9541L15.6114 19.0283C15.5077 19.1078 15.4204 19.2075 15.3546 19.3213C15.2668 19.4731 15.22 19.646 15.2198 19.8213V20C15.2198 20.7955 14.9043 21.5585 14.3419 22.1211C13.7793 22.6836 13.0154 23 12.2198 23H11.7803C10.9848 23 10.2208 22.6837 9.65828 22.1211C9.09594 21.5585 8.78035 20.7954 8.78035 20V19.8213L8.77156 19.6904C8.75435 19.5609 8.71141 19.4352 8.64558 19.3213C8.57971 19.2075 8.49243 19.1078 8.38874 19.0283L8.27742 18.9541L7.84968 18.7051C7.69776 18.6175 7.52506 18.5723 7.34968 18.5723C7.21824 18.5723 7.08852 18.5985 6.96785 18.6484L6.84968 18.7061C6.84011 18.7116 6.83013 18.7175 6.82038 18.7227L6.67097 18.8027L6.66999 18.8008C5.98771 19.1844 5.18286 19.2883 4.42585 19.0859C3.75418 18.9063 3.16836 18.4998 2.7657 17.9404L2.60456 17.6914L2.38484 17.3105L2.38386 17.3096C1.98692 16.6214 1.87897 15.8037 2.08406 15.0361C2.28803 14.2733 2.78502 13.6217 3.46589 13.2227L3.62995 13.124L3.73933 13.0508C3.84344 12.9708 3.93128 12.8704 3.99714 12.7559C4.08484 12.6032 4.13065 12.43 4.12995 12.2539V11.7461L4.12117 11.6162C4.10349 11.4873 4.06003 11.3622 3.99421 11.249C3.92841 11.136 3.84152 11.037 3.73835 10.958L3.62995 10.8857L3.57527 10.8516L3.42585 10.752C2.76614 10.351 2.2839 9.71122 2.08406 8.96387C1.87897 8.19633 1.98692 7.37864 2.38386 6.69043L2.38484 6.68848L2.60456 6.30859L2.7657 6.05957C3.16836 5.50016 3.75418 5.09371 4.42585 4.91406C5.1827 4.71172 5.9878 4.81485 6.66999 5.19824L6.67097 5.19727L6.82038 5.27734L6.84968 5.29395L6.967Line truncated
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 188 B |
@@ -32,17 +32,41 @@
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>AmneziaVPNLaunchScreen</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>QIOSWindowSceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array/>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "core/osSignalHandler.h"
|
||||
#include "migrations.h"
|
||||
#include "version.h"
|
||||
|
||||
@@ -44,6 +45,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
AmneziaApplication app(argc, argv);
|
||||
OsSignalHandler::setup();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QHostAddress>
|
||||
@@ -12,12 +15,13 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocalSocket>
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
#include "ipaddress.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "models/server.h"
|
||||
#include "daemon/daemonerrors.h"
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
@@ -115,7 +119,6 @@ void LocalSocketController::daemonConnected() {
|
||||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QString protocolName = rawConfig.value("protocol").toString();
|
||||
|
||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||
@@ -132,13 +135,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||
m_deviceIpv4 = wgConfig.value(amnezia::config_key::client_ip).toString();
|
||||
|
||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||
|
||||
// simply "dead::1" is globally-routable, don't use it
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1");
|
||||
|
||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||
@@ -220,7 +226,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||
if (splitTunnelType == 2) {
|
||||
@@ -255,10 +260,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||
@@ -274,11 +275,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()) {
|
||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||
@@ -295,10 +292,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
}
|
||||
|
||||
write(json);
|
||||
@@ -449,6 +442,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||
}
|
||||
|
||||
if (type == "status") {
|
||||
|
||||
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
|
||||
if (!serverIpv4Gateway.isString()) {
|
||||
logger.error() << "Unexpected serverIpv4Gateway value";
|
||||
@@ -493,6 +487,11 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
||||
|
||||
logger.debug() << "Handshake completed with:"
|
||||
<< pubkey.toString();
|
||||
|
||||
checkStatus();
|
||||
|
||||
emit statusUpdated("", m_deviceIpv4, 0, 0);
|
||||
|
||||
emit connected(pubkey.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "controllerimpl.h"
|
||||
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
class LocalSocketController final : public ControllerImpl {
|
||||
@@ -58,6 +59,7 @@ class LocalSocketController final : public ControllerImpl {
|
||||
|
||||
QByteArray m_buffer;
|
||||
|
||||
QString m_deviceIpv4;
|
||||
std::function<void(const QString&)> m_logCallback = nullptr;
|
||||
|
||||
QTimer m_initializingTimer;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "logger.h"
|
||||
//#include "mozillavpn.h"
|
||||
#include "networkwatcherimpl.h"
|
||||
#include "platforms/dummy/dummynetworkwatcher.h"
|
||||
//#include "settingsholder.h"
|
||||
|
||||
#ifdef MZ_WINDOWS
|
||||
@@ -51,7 +50,7 @@ NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
|
||||
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
|
||||
|
||||
void NetworkWatcher::initialize() {
|
||||
logger.debug() << "Initialize";
|
||||
logger.debug() << "Initialize NetworkWatcher";
|
||||
|
||||
#if defined(MZ_WINDOWS)
|
||||
m_impl = new WindowsNetworkWatcher(this);
|
||||
@@ -69,59 +68,45 @@ void NetworkWatcher::initialize() {
|
||||
m_impl = new DummyNetworkWatcher(this);
|
||||
#endif
|
||||
|
||||
|
||||
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
||||
&NetworkWatcher::unsecuredNetwork);
|
||||
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
||||
&NetworkWatcher::networkChange);
|
||||
|
||||
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
|
||||
&NetworkWatcher::onSleepMode);
|
||||
m_impl->initialize();
|
||||
|
||||
|
||||
// TODO: IMPL FOR AMNEZIA
|
||||
#if 0
|
||||
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||
Q_ASSERT(settingsHolder);
|
||||
|
||||
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||
settingsHolder->captivePortalAlert();
|
||||
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||
if (m_active) {
|
||||
// Enable sleep/wake monitoring for VPN auto-reconnection
|
||||
logger.debug() << "Starting NetworkWatcher for sleep/wake monitoring";
|
||||
logger.debug() << "About to call m_impl->start()";
|
||||
try {
|
||||
m_impl->start();
|
||||
logger.debug() << "m_impl->start() completed successfully";
|
||||
} catch (const std::exception& e) {
|
||||
logger.error() << "Exception in m_impl->start():" << e.what();
|
||||
} catch (...) {
|
||||
logger.error() << "Unknown exception in m_impl->start()";
|
||||
}
|
||||
|
||||
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this,
|
||||
&NetworkWatcher::settingsChanged);
|
||||
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
|
||||
&NetworkWatcher::settingsChanged);
|
||||
|
||||
#endif
|
||||
m_active = true;
|
||||
m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia
|
||||
}
|
||||
|
||||
void NetworkWatcher::settingsChanged() {
|
||||
// TODO: IMPL FOR AMNEZIA
|
||||
#if 0
|
||||
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||
settingsHolder->captivePortalAlert();
|
||||
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||
// For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring
|
||||
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
||||
}
|
||||
|
||||
if (m_active) {
|
||||
logger.debug()
|
||||
<< "Starting Network Watcher; Reporting of Unsecured Networks: "
|
||||
<< m_reportUnsecuredNetwork;
|
||||
m_impl->start();
|
||||
} else {
|
||||
logger.debug() << "Stopping Network Watcher";
|
||||
m_impl->stop();
|
||||
}
|
||||
#endif
|
||||
void NetworkWatcher::onSleepMode()
|
||||
{
|
||||
logger.debug() << "Resumed from sleep mode";
|
||||
emit sleepMode();
|
||||
}
|
||||
|
||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||
const QString& networkId) {
|
||||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||
<< "id:" << logger.sensitive(networkId);
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
if (!m_reportUnsecuredNetwork) {
|
||||
logger.debug() << "Disabled. Ignoring unsecured network";
|
||||
|
||||
@@ -29,10 +29,13 @@ public:
|
||||
// false to restore.
|
||||
void simulateDisconnection(bool simulatedDisconnection);
|
||||
|
||||
void onSleepMode();
|
||||
|
||||
QNetworkInformation::Reachability getReachability();
|
||||
|
||||
signals:
|
||||
void networkChange();
|
||||
void sleepMode();
|
||||
|
||||
private:
|
||||
void settingsChanged();
|
||||
|
||||
Loaded 100 of 301 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user