Compare commits

..

71 Commits

Author SHA1 Message Date
NickVs2015
aa42cd495f chore: bump version 2026-03-12 09:57:23 +03:00
vkamn
477afb9d85 chore: bump version (#2336) 2026-03-10 22:22:37 +08:00
NickVs2015
f969fcdbb8 fix: restore dpad functionality ATV (#2335) 2026-03-10 22:19:55 +08:00
vkamn
b0ca16d861 chore: bump version (#2331) 2026-03-09 18:29:56 +08:00
NickVs2015
9963359948 fix: disable gamepad for GP (#2321) 2026-03-09 17:39:50 +08:00
vkamn
ca639d293d chore: bump version (#2319) 2026-03-06 23:11:03 +08:00
NickVs2015
83d045af64 fix: GP requrements (#2312) 2026-03-06 17:05:16 +08:00
NickVs2015
aea8ff4961 fix: add handle handleContextCreationFailure (#2309) 2026-03-03 22:04:45 +08:00
vkamn
1892db4375 fix: remove nested qeventloop from isConfigValid (also rename to validateConfig) (#2305)
* fix: remove nested qeventloop from isConfigValid (also rename to validateConfig)

* chore: bump version
2026-03-03 20:58:32 +08:00
NickVs2015
c86a641e05 fix: add suppord android 9 gamepad and remote control (#2302) 2026-03-03 15:14:51 +08:00
vkamn
befb2bf19a chore: bump version (#2295) 2026-02-27 23:33:37 +08:00
vkamn
7ad6bc340c chore: add translations for ru (#2285)
* chore: add translations for ru

* chore: text fixes
2026-02-27 20:00:31 +08:00
vkamn
9164e38c34 fix: restore backup android (#2291)
* fix: fixed restore backup on android

* chore: add resume helper for android

* chore: add ResumeHelper.runWhenActive call after all native android dialogs

* fix: add permission for tv file picker

* fix: add file picker handler in kotlin

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2026-02-27 18:43:36 +08:00
vkamn
8f7559f01b chore: revert PR #2222 (#2290) 2026-02-27 14:29:25 +08:00
vkamn
af56200735 fix: fixed adding s3 s4 when updating the server conf for awg lagacy (#2289) 2026-02-27 14:11:40 +08:00
vkamn
3874050fae fix: again fixed s3, s4 ranges (#2288) 2026-02-27 13:37:49 +08:00
vkamn
3087163e34 fix: fixed s3, s4 ranges (#2283) 2026-02-26 22:31:41 +08:00
Mitternacht822
1fa152845c fix: generate native awg config as qr series (#2221) 2026-02-26 22:31:18 +08:00
vkamn
50e23ef233 fix: awg config update (#2281)
* fix: fixed client config update for awg container

* chore: bump version
2026-02-26 22:12:58 +08:00
Yaroslav Gurov
ea648466de chore: remove redundant VpnConnection usage from SitesController (#2278) 2026-02-26 17:55:08 +08:00
Yaroslav Gurov
b782775016 fix: change event looping to mutexes for settings and secureqsettings (#2270) 2026-02-26 11:41:08 +08:00
NickVs2015
89a7fe1081 fix: fixed remote control for ATV (#2277) 2026-02-26 11:40:16 +08:00
Yaroslav Gurov
e8bb096025 fix: ios wrong awg blob (#2272) 2026-02-24 17:56:17 +07:00
Mitternacht822
fd5c7c8322 fix: copy LICENSE to build as LICENSE.txt for WiX CPack (#2265)
* fix(installer): copy LICENSE to build as LICENSE.txt for WiX CPack

* fix: fixed a typo

* fix: fixed a typo
2026-02-24 14:07:48 +08:00
Yaroslav Gurov
e798d0f503 feat: update amneziawg-android dependencies (#2269) 2026-02-24 00:54:55 +08:00
Yaroslav Gurov
bbb0abb596 feat: update xray (#2267) 2026-02-24 00:27:29 +08:00
vkamn
0925aec86a chore: bump version (#2264) 2026-02-23 18:01:59 +08:00
Yaroslav Gurov
b084c4c284 fix: ios connection status stuck (#2263) 2026-02-23 18:00:13 +08:00
vkamn
87288ebccd chore: bump version (#2262) 2026-02-23 17:16:24 +08:00
vkamn
fcd7eadf4c chore: bump version (#2261) 2026-02-23 15:38:27 +08:00
Mitternacht822
0373338fb7 fix: randomized baseUrls traversal order in GatewayController::getProxyUrls (#2247) 2026-02-23 15:33:35 +08:00
Yaroslav Gurov
42f070fe9d fix: handle Android disconnected status properly (#2255) 2026-02-23 15:31:15 +08:00
Mitternacht822
02be6dc5f9 chore: add license to msi installer (#2227) 2026-02-20 12:13:08 +08:00
vkamn
bfcf7f0305 chore: bump version (#2244) 2026-02-19 20:27:42 +08:00
Mitternacht822
2bce595ade fix: remove revoke from remove subscription flow (#2226)
* fix(revoke): now revoke calls only for unlink device action

* fix: removed revoke call when removing a subscription from the app
2026-02-19 20:23:13 +08:00
Yaroslav Gurov
cd1e561fd4 fix: add network watcher back (#2240)
* feat: add reconnect in case of changing network

* fix: reconnect to VPN on wakeup

* fix: linux wakeup build
2026-02-19 20:21:49 +08:00
Mitternacht822
9bd1e6a0f5 fix: added stop and delete AmneziaVPNSplitTunnel on uninstall (#2222) 2026-02-18 11:21:59 +08:00
Yaroslav Gurov
5058c9aa6f fix: do not enable killswitch by default when service starts (#2232) 2026-02-18 10:59:16 +08:00
vkamn
d78416835c chore: change default i1 value (#2216) 2026-02-13 17:10:10 +08:00
vkamn
40e6c6aae3 feat: native wg with obfuscation (#2209)
* chore: change default i1 value

* feat: add i1 to native wg with obfuscation
2026-02-12 11:34:52 +08:00
Yaroslav Gurov
911a999c64 fix: xray stability and split-tunneling (#2187)
* fix: xray heap corruption

* fix: use proper configuration for split-tunneled apps

* chore: enable killswitch

* chore: xray windows split-tunneling cleanup

* chore: proper xray killswitch log

* feat: add wait for the tun device

* chore: update amnezia_xray deps for macos

* fix: add nullptr check for split-tunnel on win

* fix: modernize vpnAdapter grabbing function

* fix: remove network watcher due to its fragileness

* chore: xrayprotocol cleanup

* fix: correct wrong iface index on win

* chore: move tun2socks implementation to the client from the service

* chore: xrayprotocol cleanup

* chore: more xrayprotocol cleanup

* fix: consistent tun device with GUID specified

* chore: tun2socks logs

* chore: PrivilegedProcess cleanup
* better error handling in establishment phase
* terminate&kill ops for remote process

* fix: straighforward killing the process on windows

* fix: finally remove GUID setting from tun2socks due to instability

* fix: add sanitizer to ipc process

* chore: do not collect sensitive info from tun2socks
2026-02-11 23:47:28 +08:00
MrMirDan
b4f4184aa6 fix: returned mentioned lines (#2205) 2026-02-11 23:44:11 +08:00
NickVs2015
5c6db4b7a4 fix OpenGl error (#2185) 2026-02-10 11:15:31 +08:00
vkamn
f6277cdbb2 fix: native wg obfuscation (#2199)
* chore: bump version

* fix: fixed native wg obfuscation
2026-02-09 10:54:30 +08:00
NickVs2015
99312e61d3 fix: allow start Gamepad only Android (#2198) 2026-02-09 10:40:48 +08:00
NickVs2015
9f0ae75a2f feat: add gamepad buttons support android (#2066)
* feat: add support gamepad buttons

* feat: add support gamepad with github repo

* feat: add gitmodules dependency

* feat: add submodule qtgamepad

* chore: update qtgamepad submodule to commit 4e57142e563b931766056b4c7507c16892260222

* fix: update qtgamepad with standard CMake and private headers support

Update qtgamepad to commit f72b3e0 which:
- Replaces qt_add_library with standard add_library to avoid Qt 6.10 macro conflicts
- Copies private headers to build include tree for Android backend
- Creates Qt:: and Qt6:: namespace aliases for proper linking
2026-02-05 22:57:15 +08:00
vkamn
7960d8015d feat: add EULA and policy on IAP page (#2189) 2026-02-05 20:23:06 +08:00
vkamn
5dcc64e5e5 fix: deploy qopensslbackend on windows (#2190) 2026-02-05 20:22:47 +08:00
MrMirDan
964436ad43 fix: placeholder color, hide button image transparency, removed some lines (#2123)
* fix: placeholder color, hide button image transparency, removed unneccessary lines

* update: removed opacity on tunneling page

* update: remove opacity on app tunneling page
2026-02-05 12:56:41 +08:00
ik
4fc3900fd5 Merge pull request #2184 from amnezia-vpn/chore/add-release-date-upload
chore: add sending of release_date to s3
2026-02-04 12:20:23 +03:00
irvinklause
8f5e42dd61 chore: add sending of release_date to s3 2026-02-04 07:38:44 +00:00
Yaroslav Gurov
24895752c1 fix: added enablePeerTraffic call to xray (#2179)
* fix: add enablePeerTraffic call to xray

* chore: remove unnecessary steps during xray TUN setup phase

* chore: move tun init from tun2socks code to ipcserver

* chore: rework xray routing
* get rid of redundant delays
* check if remote calls are successful

* chore: xray routing fine-tuning

* fix: add service qt deps to deployment build
2026-02-04 12:35:53 +08:00
vkamn
87eccfb4ca fix: fix scrolling on drawers (#2183) 2026-02-04 12:35:17 +08:00
ik
a983d0504e fix: add checks for script components to find out where it can fall (#2169) 2026-01-30 14:43:30 +08:00
vkamn
d0b8535395 fix: update tag deploy (#2168) 2026-01-30 13:15:50 +08:00
dpamnezia
f84480cf56 chore: fix artifacts upload (#1961) 2026-01-30 12:43:21 +08:00
MrMirDan
de7a026ec1 fix: change drawer parents interactivity (#2004)
* fix: change drawer parents interactivity

* update: better vars names
2026-01-30 12:42:53 +08:00
MrMirDan
a128c7d247 fix: keyboard navigation (#2023)
* fix: self-hosted easy install card

* fix: label double click when enter/return pressed
2026-01-30 12:42:29 +08:00
MrMirDan
f316f0e25a feat: news notifications switch (#2126)
* feat: news notifications switch

* update: text changes

* fix: notifications enabled by default
2026-01-30 12:19:50 +08:00
NickVs2015
ea5242e29b fix: fixed cipher selection (#2110) 2026-01-30 12:18:54 +08:00
NickVs2015
b31a62c55f feat: add support open files by atv (#2082) 2026-01-30 12:11:26 +08:00
yyy-amnezia
02e3107a23 feat: implement service kickstart and improve macos post install script (#2131) 2026-01-30 12:05:20 +08:00
lunardunno
1862850108 feat: checking linux kernel version when installing amneziawg-go (#2098)
* Checking Linux kernel version when installing amneziawg-go

print the Linux kernel version to stdOut for subsequent checking by the server controller.

* Add error for old linux kernel

Add error 214 ServerLinuxKernelTooOld

* Add case for old linux kernel

Add case for error 214 ServerLinuxKernelTooOld

* Added kernel check for Awg2

Added Linux kernel version check and introduced corresponding ServerLinuxKernelTooOld error for Awg2.
2026-01-30 12:04:27 +08:00
vkamn
f73792844c chore: revoke #2148 (#2160) 2026-01-26 19:39:47 +08:00
Yaroslav Gurov
a7199ca6f5 fix: add +x permissions to wireguard-go on linux (#2159) 2026-01-26 19:16:39 +08:00
vkamn
5e757cdd3b chore: bump qt version for linux build (#2157) 2026-01-25 21:35:16 +08:00
vkamn
92af1f3268 chore: runners (#2150)
* chore: change runner for linux and android

* chore: add libsecret to linux build

* chore: bump version
2026-01-23 12:05:31 +08:00
Yaroslav Gurov
aad9d6dae2 chore: remove redundant gateway (#2148) 2026-01-22 18:21:15 +08:00
Yaroslav Gurov
423fe3fd4f fix: remove redundant gateway from xrayprotocol (#2147) 2026-01-22 18:03:36 +08:00
vkamn
b591dd7445 fix: minor fixes (#2137)
* refactor: removed premv1 migration code

* fix: i1-i5 parsing when scaning server

* chore: bump version
2026-01-19 14:03:54 +08:00
vkamn
a45bb5ea4f chore: bump version (#2108)
* chore: bump version

* chore: fix deploy.yml

* chore: return jurplel/install-qt-action@v3

* chore: bump qt version

* chore: disable cache

* chore: fix qt bin folder path

* chore: downgraded qt version for linux

* chore: disable gradle cache

* chore: use large runner for linux and android

* chore: change runner name for android and linux

* fix: change github runner label

* fix: set github runner specific os version in label

* chore: add self-hosted runner ubuntu-24.04-4cores

* fix: changed label to self-hosted for github runners

* fix: changed label to 4-core for github runners

* fix: fixed app closing delay

* fix: fixed awg description

* chore: bump version

---------

Co-authored-by: irvinklause <ik@amnezia.org>
2026-01-15 15:48:48 +08:00
155 changed files with 1806 additions and 10076 deletions

View File

@@ -10,10 +10,10 @@ env:
jobs:
Build-Linux-Ubuntu:
runs-on: ubuntu-22.04
runs-on: android-runner
env:
QT_VERSION: 6.9.2
QT_VERSION: 6.10.1
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -30,13 +30,15 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
arch: 'linux_gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -51,12 +53,12 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
sudo apt-get install libxkbcommon-x11-0
sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh
@@ -91,7 +93,7 @@ jobs:
runs-on: windows-latest
env:
QT_VERSION: 6.9.2
QT_VERSION: 6.10.1
QIF_VERSION: 4.7
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -117,8 +119,8 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -126,13 +128,15 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2019_64'
arch: 'win64_msvc2022_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1
@@ -158,7 +162,7 @@ jobs:
shell: cmd
run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_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
@@ -195,7 +199,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.9.2
QT_VERSION: 6.10.1
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -254,8 +258,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install dependencies'
run: pip install jsonschema jinja2
@@ -346,8 +350,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -374,7 +378,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.9.2
QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -412,15 +416,11 @@ 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'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
cache: 'true'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -435,8 +435,8 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -467,7 +467,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.9.2
QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -519,8 +519,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -537,7 +537,7 @@ jobs:
# ------------------------------------------------------
Build-Android:
runs-on: ubuntu-latest
runs-on: android-runner
env:
ANDROID_BUILD_PLATFORM: android-36
@@ -629,15 +629,15 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java'
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
# cache: 'gradle'
- name: 'Setup Android NDK'
id: setup-ndk

View File

@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag
run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else

3
.gitignore vendored
View File

@@ -140,3 +140,6 @@ ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12

4
.gitmodules vendored
View File

@@ -14,3 +14,7 @@
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.12.5)
set(AMNEZIAVPN_VERSION 4.8.14.6)
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 2101)
set(APP_ANDROID_VERSION_CODE 2118)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -61,6 +61,9 @@ if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")

1
client/3rd/qtgamepad vendored Submodule

Submodule client/3rd/qtgamepad added at f72b3e0c62

View File

@@ -59,7 +59,6 @@ target_include_directories(${PROJECT} PUBLIC
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)
endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
@@ -79,6 +78,7 @@ set(AMNEZIAVPN_TS_FILES
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
@@ -105,9 +105,6 @@ 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
@@ -197,7 +194,7 @@ elseif(APPLE)
include(cmake/macos.cmake)
endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS} webview)
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
# deploy artifacts required to run the application to the debug build folder
@@ -231,4 +228,13 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()

View File

@@ -15,11 +15,6 @@
#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"
@@ -66,10 +61,10 @@ 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);
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
}
#endif
@@ -82,7 +77,6 @@ AmneziaApplication::~AmneziaApplication()
}
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
@@ -104,14 +98,6 @@ 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,
@@ -143,7 +129,6 @@ 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);

View File

@@ -26,6 +26,8 @@ import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.provider.OpenableColumns
import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -73,6 +75,8 @@ private const val OPEN_FILE_ACTION_CODE = 3
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
class AmneziaActivity : QtActivity() {
@@ -89,6 +93,12 @@ class AmneziaActivity : QtActivity() {
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false
private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper())
private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false
private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
@@ -190,11 +200,18 @@ class AmneziaActivity : QtActivity() {
doBindService()
}
)
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
openFileDeliveryScheduled = false
registerBroadcastReceivers()
intent?.let(::processIntent)
runBlocking { vpnProto = proto.await() }
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
}
private fun loadLibs() {
listOf(
"rsapss",
@@ -260,6 +277,11 @@ class AmneziaActivity : QtActivity() {
}
override fun onStop() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Stop Amnezia activity")
doUnbindService()
mainScope.launch {
@@ -271,35 +293,103 @@ class AmneziaActivity : QtActivity() {
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
// Cancel pending operations if window loses focus
if (!hasFocus) {
resumeHandler.removeCallbacksAndMessages(null)
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT -> {
nativeGamepadKeyEvent(0, keyCode, pressed)
return true
}
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val synthetic = KeyEvent(
event.downTime, event.eventTime, event.action, syntheticKeyCode,
event.repeatCount, event.metaState, -1, event.scanCode,
event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
super.onPause()
isActivityResumed = false
// Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() {
super.onResume()
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity")
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
val uri = pendingOpenFileUri!!
openFileDeliveryScheduled = true
resumeHandler.postDelayed({
if (!isFinishing && !isDestroyed) {
pendingOpenFileUri = null
openFileDeliveryScheduled = false
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
postDelayed({
sendTouch(1f, 1f)
resumeHandler.postDelayed({
// Check if activity is still resumed and has focus before executing
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 100)
postDelayed({
sendTouch(2f, 2f)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
}
}, 200)
postDelayed({
requestLayout()
invalidate()
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
requestLayout()
invalidate()
}
}, 250)
}
} */
Log.d(TAG, "Resume Amnezia activity")
}
}
private fun configureWindowForEdgeToEdge() {
@@ -337,31 +427,35 @@ class AmneziaActivity : QtActivity() {
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() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity is destroyed
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
notificationStateReceiver = null
@@ -687,9 +781,13 @@ class AmneziaActivity : QtActivity() {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
if (uri.isNotEmpty()) {
pendingOpenFileUri = uri
} else {
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
}
))

View File

@@ -1,7 +1,10 @@
package org.amnezia.vpn
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
@@ -11,8 +14,29 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
setResult(RESULT_OK, Intent().apply { data = it })
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) {
setResult(RESULT_OK, Intent().apply {
data = it
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish()
}
@@ -31,7 +55,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch("*/*")
fileChooseResultLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })

View File

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

View File

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

View File

@@ -83,6 +83,26 @@ add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
endif()
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
endif()
endif()
set(LIBS ${LIBS} qt6keychain)
include_directories(

View File

@@ -163,7 +163,7 @@ add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"

View File

@@ -181,7 +181,6 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/ipcclient.h
${CLIENT_ROOT_DIR}/core/privileged_process.h
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
@@ -194,7 +193,6 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
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

View File

@@ -127,7 +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.") },
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::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.") },

View File

@@ -135,7 +135,7 @@ void CoreController::initControllers()
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
@@ -154,9 +154,6 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
}
@@ -231,8 +228,6 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler();
}
@@ -373,7 +368,11 @@ void CoreController::initPrepareConfigHandler()
return;
}
if (!m_installController->isConfigValid()) {
m_installController->validateConfig();
});
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
if (!isValid) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return;
}
@@ -382,25 +381,6 @@ void CoreController::initPrepareConfigHandler()
});
}
void CoreController::initImportPremiumV2VpnKeyHandler()
{
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
m_importController->extractConfigFromData(vpnKey);
m_importController->importConfig();
emit m_apiPremV1MigrationController->migrationFinished();
});
}
void CoreController::initShowMigrationDrawerHandler()
{
QTimer::singleShot(1000, this, [this]() {
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
m_apiPremV1MigrationController->showMigrationDrawer();
}
});
}
void CoreController::initStrictKillSwitchHandler()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),

View File

@@ -11,7 +11,6 @@
#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"
@@ -93,8 +92,6 @@ private:
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
@@ -122,7 +119,6 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel;

View File

@@ -337,6 +337,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;

View File

@@ -419,6 +419,18 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
return ErrorCode::ServerLinuxKernelTooOld;
}
}
}
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))

View File

@@ -61,6 +61,7 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors
SshRequestDeniedError = 300,

View File

@@ -29,6 +29,7 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;

View File

@@ -7,7 +7,6 @@ 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::Instance()
@@ -33,68 +32,43 @@ QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
return rep;
}
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
{
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
if (rep.isNull()) {
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
return nullptr;
}
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()
{
QSharedPointer<IpcInterfaceReplica> rep = Interface();
if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
return nullptr;
}
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
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) {
qWarning() << "Acquire PrivilegedProcess failed";
} else {
pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "PrivilegedProcess replica is not connected!";
}
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
[pd]() { pd->replicaNode->deleteLater(); });
return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
auto createPrivilegedProcess = iface->createPrivilegedProcess();
if (!createPrivilegedProcess.waitForFinished()) {
qCritical() << "Failed to create privileged process";
return nullptr;
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
const int pid = createPrivilegedProcess.returnValue();
auto* node = new QRemoteObjectNode();
node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
QSharedPointer<IpcProcessInterfaceReplica> rep(
node->acquire<IpcProcessInterfaceReplica>(),
[node] (IpcProcessInterfaceReplica *ptr) {
delete ptr;
node->deleteLater();
}
);
if (rep.isNull()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
return nullptr;
}
if (!rep->waitForSource()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
return nullptr;
}
return rep;
},
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
return nullptr;
}
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;
});
}

View File

@@ -5,9 +5,7 @@
#include <QObject>
#include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h"
#include "privileged_process.h"
#include "rep_ipc_process_interface_replica.h"
class IpcClient : public QObject
{
@@ -18,8 +16,7 @@ public:
static IpcClient& Instance();
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
template <typename Func>
static auto withInterface(Func func)
@@ -54,18 +51,6 @@ signals:
private:
QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface;
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
struct ProcessDescriptor {
ProcessDescriptor () {
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
ipcProcess = QSharedPointer<PrivilegedProcess>();
localSocket = QSharedPointer<QLocalSocket>();
}
QSharedPointer<PrivilegedProcess> ipcProcess;
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
};
#endif // IPCCLIENT_H

View File

@@ -1,27 +0,0 @@
#include "privileged_process.h"
PrivilegedProcess::PrivilegedProcess() :
IpcProcessInterfaceReplica()
{
}
PrivilegedProcess::~PrivilegedProcess()
{
qDebug() << "PrivilegedProcess::~PrivilegedProcess()";
}
void PrivilegedProcess::waitForFinished(int msecs)
{
QSharedPointer<QEventLoop> loop(new QEventLoop);
connect(this, &PrivilegedProcess::finished, this, [this, loop](int exitCode, QProcess::ExitStatus exitStatus) mutable{
loop->quit();
loop.clear();
});
QTimer::singleShot(msecs, this, [this, loop]() mutable {
loop->quit();
loop.clear();
});
loop->exec();
}

View File

@@ -1,24 +0,0 @@
#ifndef PRIVILEGED_PROCESS_H
#define PRIVILEGED_PROCESS_H
#include <QObject>
#include "rep_ipc_process_interface_replica.h"
// This class is dangerous - instance of this class casted from base class,
// so it support only functions
// Do not add any members into it
//
class PrivilegedProcess : public IpcProcessInterfaceReplica
{
Q_OBJECT
public:
PrivilegedProcess();
~PrivilegedProcess() override;
void waitForFinished(int msecs);
};
#endif // PRIVILEGED_PROCESS_H

View File

@@ -1,124 +0,0 @@
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)

View File

@@ -1,434 +0,0 @@
#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;
}

View File

@@ -1,78 +0,0 @@
#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

View File

@@ -1,72 +0,0 @@
#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

View File

@@ -1,751 +0,0 @@
#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

View File

@@ -1,309 +0,0 @@
#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

View File

@@ -1,369 +0,0 @@
#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);
}
}

View File

@@ -1,433 +0,0 @@
#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)
}

View File

@@ -1,96 +0,0 @@
#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

View File

@@ -1,423 +0,0 @@
#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();
}

View File

@@ -1,187 +0,0 @@
#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

View File

@@ -1,494 +0,0 @@
#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);
}

View File

@@ -1,109 +0,0 @@
#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

View File

@@ -1,43 +0,0 @@
#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));
}

View File

@@ -1,18 +0,0 @@
#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

View File

@@ -1,10 +0,0 @@
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QFile>
#include <QtCore/QByteArray>
#include "filehandler.h"
FileHandler::FileHandler()
{
}

View File

@@ -1,135 +0,0 @@
#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

View File

@@ -1,432 +0,0 @@
#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&paramName2=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(), &params[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;
}

View File

@@ -1,41 +0,0 @@
#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

View File

@@ -1,24 +0,0 @@
#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() {}

View File

@@ -1,185 +0,0 @@
#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

View File

@@ -1,274 +0,0 @@
#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;
}

View File

@@ -1,10 +0,0 @@
#ifndef MIMECACHE_H
#define MIMECACHE_H
class QString;
class QUrl;
QString mimeTypeForExtension(const QString &extension);
QString mimeTypeForUrl(const QUrl &url);
#endif

View File

@@ -1,66 +0,0 @@
/****************************************************************************
**
** 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

View File

@@ -1,17 +0,0 @@
#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

View File

@@ -1,15 +0,0 @@
#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

View File

@@ -1,2 +0,0 @@
module AmneziaWebView
plugin webview

View File

@@ -1,37 +0,0 @@
#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));
}

View File

@@ -1,18 +0,0 @@
#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

View File

@@ -1,10 +0,0 @@
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QFile>
#include <QtCore/QByteArray>
#include "qrchandler.h"
#include "mimecache.h"
QrcHandler::QrcHandler()
{}

View File

@@ -1,189 +0,0 @@
#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

View File

@@ -1,202 +0,0 @@
#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();
}
}

View File

@@ -1,144 +0,0 @@
#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

View File

@@ -1,3 +0,0 @@
{
"Keys": [ "AmneziaWebView" ]
}

View File

@@ -270,12 +270,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
&& !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::transportPacketMagicHeader).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));

View File

@@ -72,9 +72,9 @@ void NetworkWatcher::initialize() {
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChange);
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
&NetworkWatcher::onSleepMode);
&NetworkWatcher::networkChanged);
connect(m_impl, &NetworkWatcherImpl::wakeup, this,
&NetworkWatcher::wakeup);
m_impl->initialize();
// Enable sleep/wake monitoring for VPN auto-reconnection
@@ -97,12 +97,6 @@ void NetworkWatcher::settingsChanged() {
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
}
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)

View File

@@ -29,13 +29,11 @@ public:
// false to restore.
void simulateDisconnection(bool simulatedDisconnection);
void onSleepMode();
QNetworkInformation::Reachability getReachability();
signals:
void networkChange();
void sleepMode();
void networkChanged();
void wakeup();
private:
void settingsChanged();

View File

@@ -41,7 +41,7 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should
// too.
void networkChanged(QString newBSSID);
void sleepMode();
void wakeup();
private:

View File

@@ -41,8 +41,8 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork);
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
&NetworkWatcherImpl::sleepMode);
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::wakeup);
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because

View File

@@ -200,7 +200,7 @@ void LinuxNetworkWatcherWorker::checkDevices() {
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{
if (state == NM_STATE_ASLEEP) {
emit sleepMode();
emit wakeup();
}
logger.debug() << "NMStateChanged " << state;

View File

@@ -23,7 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId);
void sleepMode();
void wakeup();
public slots:
void initialize();

View File

@@ -173,10 +173,10 @@ void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_
case kIOMessageSystemHasPoweredOn:
/* Announces that the system and its devices have woken up. */
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
if (listener->m_watcher) {
// Use QMetaObject::invokeMethod for thread-safe signal emission
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
}
break;

View File

@@ -62,6 +62,9 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
}
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (m_splitTunnelManager == nullptr)
return;
if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);

View File

@@ -41,7 +41,7 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
switch (uMsg) {
case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) {
emit obj->sleepMode();
emit obj->wakeup();
}
break;
default:

View File

@@ -232,12 +232,6 @@ ErrorCode OpenVpnProtocol::start()
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->waitForSource(5000);
if (!m_openVpnProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
QStringList arguments({
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
@@ -246,13 +240,13 @@ ErrorCode OpenVpnProtocol::start()
m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred,
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged,
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this,
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_openVpnProcess->start();

View File

@@ -53,7 +53,7 @@ private:
void updateRouteGateway(QString line);
void updateVpnGateway(const QString &line);
QSharedPointer<PrivilegedProcess> m_openVpnProcess;
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
};
#endif // OPENVPNPROTOCOL_H

View File

@@ -233,7 +233,7 @@ namespace amnezia
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
constexpr char defaultSpecialJunk1[] = "<b 0x084481800001000300000000077469636b65747306776964676574096b696e6f706f69736b0272750000010001c00c0005000100000039001806776964676574077469636b6574730679616e646578c025c0390005000100000039002b1765787465726e616c2d7469636b6574732d776964676574066166697368610679616e646578036e657400c05d000100010000001c000457fafe25>";
constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
constexpr char defaultSpecialJunk2[] = "";
constexpr char defaultSpecialJunk3[] = "";
constexpr char defaultSpecialJunk4[] = "";

View File

@@ -15,7 +15,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
emit connectionStateChanged(Vpn::ConnectionState::Connected);
setConnectionState(Vpn::ConnectionState::Connected);
});
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
[this](const QString& serverIpv4Gateway,
@@ -38,7 +38,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
});
connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr);
}

View File

@@ -1,6 +1,7 @@
#include "xrayprotocol.h"
#include "core/ipcclient.h"
#include "ipc.h"
#include "utilities.h"
#include "core/networkUtilities.h"
@@ -9,14 +10,37 @@
#include <QJsonObject>
#include <QNetworkInterface>
#include <QJsonDocument>
#include <QtCore/qlogging.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qprocess.h>
#ifdef Q_OS_MACOS
static const QString tunName = "utun22";
#else
static const QString tunName = "tun2";
#endif
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{
readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
m_t2sProcess = IpcClient::InterfaceTun2Socks();
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString());
const QString primaryDns = configuration.value(amnezia::config_key::dns1).toString();
m_dnsServers.push_back(QHostAddress(primaryDns));
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
const QString secondaryDns = configuration.value(amnezia::config_key::dns2).toString();
m_dnsServers.push_back(QHostAddress(secondaryDns));
}
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
}
m_xrayConfig = xrayConfiguration;
}
XrayProtocol::~XrayProtocol()
@@ -29,72 +53,16 @@ ErrorCode XrayProtocol::start()
{
qDebug() << "XrayProtocol::start()";
const ErrorCode err = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
return ErrorCode::NoError;
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray";
return ErrorCode::XrayExecutableCrashed;
}
return startTun2Socks();
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
if (err != ErrorCode::NoError)
return err;
setConnectionState(Vpn::ConnectionState::Connecting);
return startTun2Sock();
}
ErrorCode XrayProtocol::startTun2Sock()
{
m_t2sProcess->start();
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (vpnState == Vpn::ConnectionState::Connected) {
setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_primaryDNS));
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsAddr.push_back(QHostAddress(m_secondaryDNS));
}
#ifdef Q_OS_WIN
QThread::msleep(8000);
#endif
#ifdef Q_OS_MACOS
QThread::msleep(5000);
iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
iface->updateResolvers("utun22", dnsAddr);
#endif
#ifdef Q_OS_LINUX
QThread::msleep(1000);
iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
iface->updateResolvers("tun2", dnsAddr);
#endif
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
iface->routeAddList(m_vpnGateway, QStringList() << "1.0.0.0/8" << "2.0.0.0/7" << "4.0.0.0/6" << "8.0.0.0/5" << "16.0.0.0/4" << "32.0.0.0/3" << "64.0.0.0/2" << "128.0.0.0/1");
}
iface->StopRoutingIpv6();
#ifdef Q_OS_WIN
iface->updateResolvers("tun2", dnsAddr);
#endif
setConnectionState(Vpn::ConnectionState::Connected);
}
#if !defined(Q_OS_MACOS)
if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected);
iface->deleteTun("tun2");
iface->StartRoutingIpv6();
iface->clearSavedRoutes();
}
#endif
});
});
return ErrorCode::NoError;
}
void XrayProtocol::stop()
@@ -102,43 +70,177 @@ void XrayProtocol::stop()
qDebug() << "XrayProtocol::stop()";
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
#ifdef AMNEZIA_DESKTOP
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = iface->StartRoutingIpv6();
if (!StartRoutingIpv6Resp.waitForFinished(1000)) {
qWarning() << "XrayProtocol::stop(): Failed to start routing ipv6";
}
auto disableKillSwitch = iface->disableKillSwitch();
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
qWarning() << "Failed to disable killswitch";
QRemoteObjectPendingReply<bool> restoreResolvers = iface->restoreResolvers();
if (!restoreResolvers.waitForFinished(1000)) {
qWarning() << "XrayProtocol::stop(): Failed to restore resolvers";
}
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
qWarning() << "Failed to start routing ipv6";
#if !defined(Q_OS_MACOS)
QRemoteObjectPendingReply<bool> deleteTunResp = iface->deleteTun("tun2");
if (!deleteTunResp.waitForFinished(1000)) {
qWarning() << "XrayProtocol::stop(): Failed to delete tun";
}
#endif
#endif
iface->xrayStop();
auto restoreResolvers = iface->restoreResolvers();
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
qWarning() << "Failed to restore resolvers";
auto deleteTun = iface->deleteTun(tunName);
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
qWarning() << "Failed to delete tun";
auto xrayStop = iface->xrayStop();
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
qWarning() << "Failed to stop xray";
});
if (m_t2sProcess) {
m_t2sProcess->stop();
QThread::msleep(200);
if (m_tun2socksProcess) {
m_tun2socksProcess->blockSignals(true);
#ifndef Q_OS_WIN
m_tun2socksProcess->terminate();
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
qWarning() << "Failed to terminate tun2socks. Killing the process...";
m_tun2socksProcess->kill();
}
#else
// terminate does not do anything useful on Windows
// so just kill the process
m_tun2socksProcess->kill();
#endif
m_tun2socksProcess->close();
m_tun2socksProcess.reset();
}
setConnectionState(Vpn::ConnectionState::Disconnected);
}
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
ErrorCode XrayProtocol::startTun2Socks()
{
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
if (!m_tun2socksProcess->waitForSource()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_xrayConfig = xrayConfiguration;
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", "socks5://127.0.0.1:10808" });
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() {
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
if (!readAllStandardOutput.waitForFinished()) {
qWarning() << "Failed to read output from tun2socks";
return;
}
const QString line = readAllStandardOutput.returnValue();
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
qDebug() << "[tun2socks]:" << line;
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) {
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
stop();
setLastError(res);
} else {
setConnectionState(Vpn::ConnectionState::Connected);
}
}
}, Qt::QueuedConnection);
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::ExitStatus::CrashExit) {
qCritical() << "Tun2socks process crashed!";
} else {
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
}
stop();
setLastError(ErrorCode::Tun2SockExecutableCrashed);
}, Qt::QueuedConnection);
m_tun2socksProcess->start();
return ErrorCode::NoError;
}
ErrorCode XrayProtocol::setupRouting() {
return IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
#ifdef Q_OS_WIN
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
#endif
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
if (!createTun.waitForFinished() || !createTun.returnValue()) {
qCritical() << "Failed to assign IP address for TUN";
return ErrorCode::InternalError;
}
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
qCritical() << "Failed to set DNS resolvers for TUN";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
int vpnAdapterIndex = -1;
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (auto& netInterface : netInterfaces) {
for (auto& address : netInterface.addressEntries()) {
if (m_vpnLocalAddress == address.ip().toString())
vpnAdapterIndex = netInterface.index();
}
}
#else
static const int vpnAdapterIndex = 0;
#endif
const bool killSwitchEnabled = QVariant(m_rawConfig.value(config_key::killSwitchOption).toString()).toBool();
if (killSwitchEnabled) {
if (vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("vpnServer", m_remoteAddress);
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
qCritical() << "Failed to enable killswitch";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
}
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
qCritical() << "Failed to set routes for TUN";
return ErrorCode::InternalError;
}
}
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
qCritical() << "Failed to disable IPv6 routing";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("inetAdapterIndex", inetAdapterIndex);
config.insert("vpnAdapterIndex", vpnAdapterIndex);
config.insert("vpnGateway", m_vpnGateway);
config.insert("vpnServer", m_remoteAddress);
auto enablePeerTraffic = iface->enablePeerTraffic(config);
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
qCritical() << "Failed to enable peer traffic";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
#endif
return ErrorCode::NoError;
},
[] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
}

View File

@@ -6,6 +6,7 @@
#include "core/ipcclient.h"
#include "vpnprotocol.h"
#include "settings.h"
#include <QtCore/qsharedpointer.h>
class XrayProtocol : public VpnProtocol
{
@@ -14,19 +15,18 @@ public:
virtual ~XrayProtocol() override;
ErrorCode start() override;
ErrorCode startTun2Sock();
void stop() override;
private:
void readXrayConfiguration(const QJsonObject &configuration);
ErrorCode setupRouting();
ErrorCode startTun2Socks();
QJsonObject m_xrayConfig;
Settings::RouteMode m_routeMode;
QString m_primaryDNS;
QString m_secondaryDNS;
#ifndef Q_OS_IOS
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
#endif
QList<QHostAddress> m_dnsServers;
QString m_remoteAddress;
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
};
#endif // XRAYPROTOCOL_H

View File

@@ -129,6 +129,7 @@
<file>ui/qml/Components/AdLabel.qml</file>
<file>ui/qml/Components/ConnectButton.qml</file>
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
<file>ui/qml/Components/GamepadLoader.qml</file>
<file>ui/qml/Components/HomeContainersListView.qml</file>
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
@@ -163,7 +164,6 @@
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
<file>ui/qml/Controls2/PageType.qml</file>
<file>ui/qml/Controls2/PopupType.qml</file>
<file>ui/qml/Controls2/PremiumBannerType.qml</file>
<file>ui/qml/Controls2/ProgressBarType.qml</file>
<file>ui/qml/Controls2/ScrollBarType.qml</file>
<file>ui/qml/Controls2/StackViewType.qml</file>
@@ -227,7 +227,6 @@
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
<file>ui/qml/Pages2/PageSetupWizardPremiumWebView.qml</file>
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
<file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
@@ -249,16 +248,9 @@
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
<file>images/controls/monitor.svg</file>
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
<file>ui/qml/Components/SmartScroll.qml</file>
<file>ui/qml/Components/AmneziaWebViewPanel.qml</file>
</qresource>
<qresource prefix="/AmneziaWebView">
<file>core/webview/qmldir</file>
</qresource>
<qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file>

View File

@@ -35,13 +35,12 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
}
}
m_settings.setValue("Conf/encrypted", true);
m_settings.sync();
}
}
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
{
QMutexLocker locker(&mutex);
QMutexLocker locker(&m_mutex);
if (m_cache.contains(key)) {
return m_cache.value(key);
@@ -85,7 +84,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
void SecureQSettings::setValue(const QString &key, const QVariant &value)
{
QMutexLocker locker(&mutex);
QMutexLocker locker(&m_mutex);
if (encryptionRequired() && encryptedKeys.contains(key)) {
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
@@ -107,26 +106,20 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
}
m_cache.insert(key, value);
sync();
}
void SecureQSettings::remove(const QString &key)
{
QMutexLocker locker(&mutex);
QMutexLocker locker(&m_mutex);
m_settings.remove(key);
m_cache.remove(key);
sync();
}
void SecureQSettings::sync()
{
m_settings.sync();
}
QByteArray SecureQSettings::backupAppConfig() const
{
QMutexLocker locker(&m_mutex);
QJsonObject cfg;
const auto needToBackup = [this](const auto &key) {
@@ -161,6 +154,8 @@ QByteArray SecureQSettings::backupAppConfig() const
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
{
QMutexLocker locker(&m_mutex);
QJsonObject cfg = QJsonDocument::fromJson(json).object();
if (cfg.isEmpty())
return false;
@@ -173,10 +168,16 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
setValue(key, cfg.value(key).toVariant());
}
sync();
return true;
}
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&m_mutex);
m_settings.clear();
m_cache.clear();
}
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{
QSimpleCrypto::QBlockCipher cipher;
@@ -294,11 +295,3 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
}
}
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&mutex);
m_settings.clear();
m_cache.clear();
sync();
}

View File

@@ -16,14 +16,16 @@ public:
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
QObject *parent = nullptr);
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
void remove(const QString &key);
void sync();
QByteArray backupAppConfig() const;
bool restoreAppConfig(const QByteArray &json);
void clearSettings();
private:
QByteArray encryptText(const QByteArray &value) const;
QByteArray decryptText(const QByteArray &ba) const;
@@ -35,9 +37,6 @@ public:
static QByteArray getSecTag(const QString &tag);
static void setSecTag(const QString &tag, const QByteArray &data);
void clearSettings();
private:
QSettings m_settings;
mutable QHash<QString, QVariant> m_cache;
@@ -53,7 +52,7 @@ private:
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
mutable QMutex mutex;
mutable QRecursiveMutex m_mutex;
};
#endif // SECUREQSETTINGS_H

View File

@@ -21,4 +21,5 @@ if [ "$(systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo systemctl start docker; sleep 5;\
fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version
docker --version;\
uname -sr

View File

@@ -21,10 +21,10 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
{
// Import old settings
if (serversCount() == 0) {
QString user = value("Server/userName").toString();
QString password = value("Server/password").toString();
QString serverName = value("Server/serverName").toString();
int port = value("Server/serverPort").toInt();
QString user = m_settings.value("Server/userName").toString();
QString password = m_settings.value("Server/password").toString();
QString serverName = m_settings.value("Server/serverName").toString();
int port = m_settings.value("Server/serverPort").toInt();
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
QJsonObject server;
@@ -222,7 +222,7 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled)
{
setValue("Conf/saveLogs", enabled);
m_settings.setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) {
Logger::deInit();
@@ -242,12 +242,12 @@ void Settings::setSaveLogs(bool enabled)
QDateTime Settings::getLogEnableDate()
{
return value("Conf/logEnableDate").toDateTime();
return m_settings.value("Conf/logEnableDate").toDateTime();
}
void Settings::setLogEnableDate(QDateTime date)
{
setValue("Conf/logEnableDate", date);
m_settings.setValue("Conf/logEnableDate", date);
}
QString Settings::routeModeString(RouteMode mode) const
@@ -261,17 +261,17 @@ QString Settings::routeModeString(RouteMode mode) const
Settings::RouteMode Settings::routeMode() const
{
return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt());
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
}
bool Settings::isSitesSplitTunnelingEnabled() const
{
return value("Conf/sitesSplitTunnelingEnabled", false).toBool();
return m_settings.value("Conf/sitesSplitTunnelingEnabled", false).toBool();
}
void Settings::setSitesSplitTunnelingEnabled(bool enabled)
{
setValue("Conf/sitesSplitTunnelingEnabled", enabled);
m_settings.setValue("Conf/sitesSplitTunnelingEnabled", enabled);
}
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
@@ -359,12 +359,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
QString Settings::primaryDns() const
{
return value("Conf/primaryDns", cloudFlareNs1).toString();
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();
}
QString Settings::secondaryDns() const
{
return value("Conf/secondaryDns", cloudFlareNs2).toString();
return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString();
}
void Settings::clearSettings()
@@ -386,18 +386,18 @@ QString Settings::appsRouteModeString(AppsRouteMode mode) const
Settings::AppsRouteMode Settings::getAppsRouteMode() const
{
return static_cast<AppsRouteMode>(value("Conf/appsRouteMode", 0).toInt());
return static_cast<AppsRouteMode>(m_settings.value("Conf/appsRouteMode", 0).toInt());
}
void Settings::setAppsRouteMode(AppsRouteMode mode)
{
setValue("Conf/appsRouteMode", mode);
m_settings.setValue("Conf/appsRouteMode", mode);
}
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
{
QVector<InstalledAppInfo> apps;
auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray();
auto appsArray = m_settings.value("Conf/" + appsRouteModeString(mode)).toJsonArray();
for (const auto &app : appsArray) {
InstalledAppInfo appInfo;
appInfo.appName = app.toObject().value("appName").toString();
@@ -419,43 +419,42 @@ void Settings::setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &a
appInfo.insert("appPath", app.appPath);
appsArray.push_back(appInfo);
}
setValue("Conf/" + appsRouteModeString(mode), appsArray);
m_settings.sync();
m_settings.setValue("Conf/" + appsRouteModeString(mode), appsArray);
}
bool Settings::isAppsSplitTunnelingEnabled() const
{
return value("Conf/appsSplitTunnelingEnabled", false).toBool();
return m_settings.value("Conf/appsSplitTunnelingEnabled", false).toBool();
}
void Settings::setAppsSplitTunnelingEnabled(bool enabled)
{
setValue("Conf/appsSplitTunnelingEnabled", enabled);
m_settings.setValue("Conf/appsSplitTunnelingEnabled", enabled);
}
bool Settings::isKillSwitchEnabled() const
{
return value("Conf/killSwitchEnabled", true).toBool();
return m_settings.value("Conf/killSwitchEnabled", true).toBool();
}
void Settings::setKillSwitchEnabled(bool enabled)
{
setValue("Conf/killSwitchEnabled", enabled);
m_settings.setValue("Conf/killSwitchEnabled", enabled);
}
bool Settings::isStrictKillSwitchEnabled() const
{
return value("Conf/strictKillSwitchEnabled", false).toBool();
return m_settings.value("Conf/strictKillSwitchEnabled", false).toBool();
}
void Settings::setStrictKillSwitchEnabled(bool enabled)
{
setValue("Conf/strictKillSwitchEnabled", enabled);
m_settings.setValue("Conf/strictKillSwitchEnabled", enabled);
}
QString Settings::getInstallationUuid(const bool needCreate)
{
auto uuid = value("Conf/installationUuid", "").toString();
auto uuid = m_settings.value("Conf/installationUuid", "").toString();
if (needCreate && uuid.isEmpty()) {
uuid = QUuid::createUuid().toString();
@@ -476,7 +475,7 @@ QString Settings::getInstallationUuid(const bool needCreate)
void Settings::setInstallationUuid(const QString &uuid)
{
setValue("Conf/installationUuid", uuid);
m_settings.setValue("Conf/installationUuid", uuid);
}
ServerCredentials Settings::defaultServerCredentials() const
@@ -497,28 +496,6 @@ ServerCredentials Settings::serverCredentials(int index) const
return credentials;
}
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
{
QVariant returnValue;
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
returnValue = m_settings.value(key, defaultValue);
} else {
QMetaObject::invokeMethod(&m_settings, "value", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, returnValue),
Q_ARG(const QString &, key), Q_ARG(const QVariant &, defaultValue));
}
return returnValue;
}
void Settings::setValue(const QString &key, const QVariant &value)
{
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
m_settings.setValue(key, value);
} else {
QMetaObject::invokeMethod(&m_settings, "setValue", Qt::BlockingQueuedConnection, Q_ARG(const QString &, key),
Q_ARG(const QVariant &, value));
}
}
void Settings::resetGatewayEndpoint()
{
m_gatewayEndpoint = gatewayEndpoint;
@@ -541,50 +518,50 @@ QString Settings::getGatewayEndpoint(bool isTestPurchase)
bool Settings::isDevGatewayEnv(bool isTestPurchase)
{
return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool();
return isTestPurchase ? true : m_settings.value("Conf/devGatewayEnv", false).toBool();
}
void Settings::toggleDevGatewayEnv(bool enabled)
{
setValue("Conf/devGatewayEnv", enabled);
m_settings.setValue("Conf/devGatewayEnv", enabled);
}
bool Settings::isHomeAdLabelVisible()
{
return value("Conf/homeAdLabelVisible", true).toBool();
return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
}
void Settings::disableHomeAdLabel()
{
setValue("Conf/homeAdLabelVisible", false);
m_settings.setValue("Conf/homeAdLabelVisible", false);
}
bool Settings::isPremV1MigrationReminderActive()
{
return value("Conf/premV1MigrationReminderActive", true).toBool();
return m_settings.value("Conf/premV1MigrationReminderActive", true).toBool();
}
void Settings::disablePremV1MigrationReminder()
{
setValue("Conf/premV1MigrationReminderActive", false);
m_settings.setValue("Conf/premV1MigrationReminderActive", false);
}
QStringList Settings::allowedDnsServers() const
{
return value("Conf/allowedDnsServers").toStringList();
return m_settings.value("Conf/allowedDnsServers").toStringList();
}
void Settings::setAllowedDnsServers(const QStringList &servers)
{
setValue("Conf/allowedDnsServers", servers);
m_settings.setValue("Conf/allowedDnsServers", servers);
}
QStringList Settings::readNewsIds() const
{
return value("News/readIds").toStringList();
return m_settings.value("News/readIds").toStringList();
}
void Settings::setReadNewsIds(const QStringList &ids)
{
setValue("News/readIds", ids);
m_settings.setValue("News/readIds", ids);
}

View File

@@ -29,11 +29,11 @@ public:
QJsonArray serversArray() const
{
return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array();
}
void setServersArray(const QJsonArray &servers)
{
setValue("Servers/serversList", QJsonDocument(servers).toJson());
m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson());
}
// Servers section
@@ -45,11 +45,11 @@ public:
int defaultServerIndex() const
{
return value("Servers/defaultServerIndex", 0).toInt();
return m_settings.value("Servers/defaultServerIndex", 0).toInt();
}
void setDefaultServer(int index)
{
setValue("Servers/defaultServerIndex", index);
m_settings.setValue("Servers/defaultServerIndex", index);
}
QJsonObject defaultServer() const
{
@@ -78,25 +78,34 @@ public:
// App settings section
bool isAutoConnect() const
{
return value("Conf/autoConnect", false).toBool();
return m_settings.value("Conf/autoConnect", false).toBool();
}
void setAutoConnect(bool enabled)
{
setValue("Conf/autoConnect", enabled);
m_settings.setValue("Conf/autoConnect", enabled);
}
bool isStartMinimized() const
{
return value("Conf/startMinimized", false).toBool();
return m_settings.value("Conf/startMinimized", false).toBool();
}
void setStartMinimized(bool enabled)
{
setValue("Conf/startMinimized", enabled);
m_settings.setValue("Conf/startMinimized", enabled);
}
bool isNewsNotifications() const
{
return m_settings.value("Conf/newsNotifications", true).toBool();
}
void setNewsNotifications(bool enabled)
{
m_settings.setValue("Conf/newsNotifications", enabled);
}
bool isSaveLogs() const
{
return value("Conf/saveLogs", false).toBool();
return m_settings.value("Conf/saveLogs", false).toBool();
}
void setSaveLogs(bool enabled);
@@ -113,19 +122,18 @@ public:
QString routeModeString(RouteMode mode) const;
RouteMode routeMode() const;
void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); }
void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); }
bool isSitesSplitTunnelingEnabled() const;
void setSitesSplitTunnelingEnabled(bool enabled);
QVariantMap vpnSites(RouteMode mode) const
{
return value("Conf/" + routeModeString(mode)).toMap();
return m_settings.value("Conf/" + routeModeString(mode)).toMap();
}
void setVpnSites(RouteMode mode, const QVariantMap &sites)
{
setValue("Conf/" + routeModeString(mode), sites);
m_settings.sync();
m_settings.setValue("Conf/" + routeModeString(mode), sites);
}
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
@@ -138,11 +146,11 @@ public:
bool useAmneziaDns() const
{
return value("Conf/useAmneziaDns", true).toBool();
return m_settings.value("Conf/useAmneziaDns", true).toBool();
}
void setUseAmneziaDns(bool enabled)
{
setValue("Conf/useAmneziaDns", enabled);
m_settings.setValue("Conf/useAmneziaDns", enabled);
}
QString primaryDns() const;
@@ -151,13 +159,13 @@ public:
// QString primaryDns() const { return m_primaryDns; }
void setPrimaryDns(const QString &primaryDns)
{
setValue("Conf/primaryDns", primaryDns);
m_settings.setValue("Conf/primaryDns", primaryDns);
}
// QString secondaryDns() const { return m_secondaryDns; }
void setSecondaryDns(const QString &secondaryDns)
{
setValue("Conf/secondaryDns", secondaryDns);
m_settings.setValue("Conf/secondaryDns", secondaryDns);
}
// static constexpr char openNicNs5[] = "94.103.153.176";
@@ -179,16 +187,16 @@ public:
};
void setAppLanguage(QLocale locale)
{
setValue("Conf/appLanguage", locale.name());
m_settings.setValue("Conf/appLanguage", locale.name());
};
bool isScreenshotsEnabled() const
{
return value("Conf/screenshotsEnabled", true).toBool();
return m_settings.value("Conf/screenshotsEnabled", true).toBool();
}
void setScreenshotsEnabled(bool enabled)
{
setValue("Conf/screenshotsEnabled", enabled);
m_settings.setValue("Conf/screenshotsEnabled", enabled);
emit screenshotsEnabledChanged(enabled);
}
@@ -246,9 +254,6 @@ signals:
void settingsCleared();
private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
void setInstallationUuid(const QString &uuid);
mutable SecureQSettings m_settings;

File diff suppressed because it is too large Load Diff

View File

@@ -382,6 +382,51 @@ bool ApiConfigsController::fillAvailableServices()
}
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
#if defined(Q_OS_IOS) || defined(MACOS_NE)
QEventLoop waitProducts;
bool productsFetched = false;
QString productPrice;
QString productCurrency;
IosController::Instance()->fetchProducts(QStringList() << QStringLiteral("amnezia_premium_6_month"),
[&](const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString) {
if (!errorString.isEmpty() || products.isEmpty()) {
qWarning().noquote() << "[IAP] Failed to fetch product price:" << errorString;
} else {
const auto &product = products.first();
productPrice = product.value("price").toString();
productCurrency = product.value("currencyCode").toString();
productsFetched = true;
qInfo().noquote() << "[IAP] Fetched product price:" << productPrice << productCurrency;
}
waitProducts.quit();
});
waitProducts.exec();
if (productsFetched && !productPrice.isEmpty()) {
QJsonArray services = data.value("services").toArray();
for (int i = 0; i < services.size(); ++i) {
QJsonObject service = services[i].toObject();
if (service.value(configKey::serviceType).toString() == serviceType::amneziaPremium) {
QJsonObject serviceInfo = service.value(configKey::serviceInfo).toObject();
QString formattedPrice = productPrice;
if (!productCurrency.isEmpty()) {
formattedPrice += " " + productCurrency;
}
serviceInfo["price"] = formattedPrice;
service[configKey::serviceInfo] = serviceInfo;
services[i] = service;
data["services"] = services;
qInfo().noquote() << "[IAP] Updated premium service price in data:" << formattedPrice;
break;
}
}
}
#endif
m_apiServicesModel->updateModel(data);
if (m_apiServicesModel->rowCount() > 0) {
m_apiServicesModel->setServiceIndex(0);

View File

@@ -1,133 +0,0 @@
#include "apiPremV1MigrationController.h"
#include <QEventLoop>
#include <QTimer>
#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/controllers/gatewayController.h"
ApiPremV1MigrationController::ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
}
bool ApiPremV1MigrationController::hasConfigsToMigration()
{
QJsonArray vpnKeys;
auto serversCount = m_serversModel->getServersCount();
for (size_t i = 0; i < serversCount; i++) {
auto serverConfigObject = m_serversModel->getServerConfig(i);
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
continue;
}
QString vpnKey = apiUtils::getPremiumV1VpnKey(serverConfigObject);
vpnKeys.append(vpnKey);
}
if (!vpnKeys.isEmpty()) {
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload["configs"] = vpnKeys;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody);
auto migrationsStatus = QJsonDocument::fromJson(responseBody).object();
for (const auto &migrationStatus : migrationsStatus) {
if (migrationStatus == "not_found") {
return true;
}
}
}
return false;
}
void ApiPremV1MigrationController::getSubscriptionList(const QString &email)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/subscription-list"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_email = email;
m_subscriptionsModel = QJsonDocument::fromJson(responseBody).array();
if (m_subscriptionsModel.isEmpty()) {
emit noSubscriptionToMigrate();
return;
}
emit subscriptionsModelChanged();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
QJsonArray ApiPremV1MigrationController::getSubscriptionModel()
{
return m_subscriptionsModel;
}
void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex)
{
QEventLoop wait;
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migration-code"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_subscriptionIndex = subscriptionIndex;
emit otpSuccessfullySent();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
void ApiPremV1MigrationController::migrate(const QString &migrationCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
apiPayload[apiDefs::key::orderId] = m_subscriptionsModel.at(m_subscriptionIndex).toObject().value(apiDefs::key::id).toString();
apiPayload[apiDefs::key::migrationCode] = migrationCode;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migrate"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
auto responseObject = QJsonDocument::fromJson(responseBody).object();
QString premiumV2VpnKey = responseObject.value(apiDefs::key::config).toString();
emit importPremiumV2VpnKey(premiumV2VpnKey);
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
bool ApiPremV1MigrationController::isPremV1MigrationReminderActive()
{
return m_settings->isPremV1MigrationReminderActive();
}
void ApiPremV1MigrationController::disablePremV1MigrationReminder()
{
m_settings->disablePremV1MigrationReminder();
}

View File

@@ -1,50 +0,0 @@
#ifndef APIPREMV1MIGRATIONCONTROLLER_H
#define APIPREMV1MIGRATIONCONTROLLER_H
#include <QObject>
#include "ui/models/servers_model.h"
class ApiPremV1MigrationController : public QObject
{
Q_OBJECT
public:
ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
Q_PROPERTY(QJsonArray subscriptionsModel READ getSubscriptionModel NOTIFY subscriptionsModelChanged)
public slots:
bool hasConfigsToMigration();
void getSubscriptionList(const QString &email);
QJsonArray getSubscriptionModel();
void sendMigrationCode(const int subscriptionIndex);
void migrate(const QString &migrationCode);
bool isPremV1MigrationReminderActive();
void disablePremV1MigrationReminder();
signals:
void subscriptionsModelChanged();
void otpSuccessfullySent();
void importPremiumV2VpnKey(const QString &vpnKey);
void errorOccurred(ErrorCode errorCode);
void showMigrationDrawer();
void migrationFinished();
void noSubscriptionToMigrate();
private:
QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
QJsonArray m_subscriptionsModel;
int m_subscriptionIndex;
QString m_email;
};
#endif // APIPREMV1MIGRATIONCONTROLLER_H

View File

@@ -7,7 +7,6 @@
#include <QFileInfo>
#include <QImage>
#include <QStandardPaths>
#include "core/controllers/vpnConfigurationController.h"
#include "core/qrCodeUtils.h"
#include "core/serialization/serialization.h"
@@ -170,8 +169,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
m_config.append(line + "\n");
}
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
emit exportConfigChanged();
}
@@ -191,8 +189,7 @@ void ExportController::generateAwgConfig(const QString &clientName)
m_config.append(line + "\n");
}
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
emit exportConfigChanged();
}

View File

@@ -291,6 +291,8 @@ void ImportController::processNativeWireGuardConfig()
clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0";
clientProtocolConfig[config_key::transportPacketJunkSize] = "0";
clientProtocolConfig[config_key::specialJunk1] = protocols::awg::defaultSpecialJunk1;
clientProtocolConfig[config_key::isObfuscationEnabled] = true;
serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson());

View File

@@ -83,8 +83,8 @@ void InstallController::install(DockerContainer container, int port, TransportPr
int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150);
int s3 = QRandomGenerator::global()->bounded(0, 64);
int s4 = QRandomGenerator::global()->bounded(0, 20);
int s3 = QRandomGenerator::global()->bounded(1, 64);
int s4 = QRandomGenerator::global()->bounded(1, 20);
// Ensure all values are unique and don't create equal packet sizes
QSet<int> usedValues;
@@ -97,12 +97,12 @@ void InstallController::install(DockerContainer container, int port, TransportPr
while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
s3 = QRandomGenerator::global()->bounded(0, 64);
s3 = QRandomGenerator::global()->bounded(1, 64);
}
usedValues.insert(s3);
while (usedValues.contains(s4)) {
s4 = QRandomGenerator::global()->bounded(0, 20);
s4 = QRandomGenerator::global()->bounded(1, 20);
}
QString initPacketJunkSize = QString::number(s1);
@@ -451,6 +451,13 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
containerConfig[config_key::transportPacketMagicHeader] =
serverConfigMap.value(config_key::transportPacketMagicHeader);
// hack to parse i1-i5 from commented lines in server config
containerConfig[config_key::specialJunk1] = serverConfigMap.value(QString("# ") + config_key::specialJunk1);
containerConfig[config_key::specialJunk2] = serverConfigMap.value(QString("# ") + config_key::specialJunk2);
containerConfig[config_key::specialJunk3] = serverConfigMap.value(QString("# ") + config_key::specialJunk3);
containerConfig[config_key::specialJunk4] = serverConfigMap.value(QString("# ") + config_key::specialJunk4);
containerConfig[config_key::specialJunk5] = serverConfigMap.value(QString("# ") + config_key::specialJunk5);
if (container == DockerContainer::Awg2) {
containerConfig[config_key::protocolVersion] = "2";
containerConfig[config_key::cookieReplyPacketJunkSize] =
@@ -980,79 +987,94 @@ void InstallController::addEmptyServer()
emit installServerFinished(tr("Server added successfully"));
}
bool InstallController::isConfigValid()
void InstallController::validateConfig()
{
int serverIndex = m_serversModel->getDefaultServerIndex();
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
if (apiUtils::isServerFromApi(serverConfigObject)) {
return true;
emit configValidated(true);
return;
}
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit noInstalledContainers();
return false;
emit configValidated(false);
return;
}
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
if (container == DockerContainer::None) {
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
return false;
emit configValidated(false);
return;
}
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
QFutureWatcher<ErrorCode> watcher;
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QString protocolConfig =
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() {
ErrorCode errorCode = ErrorCode::NoError;
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QString protocolConfig =
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
if (protocolConfig.isEmpty()) {
return false;
}
}
return true;
};
if (!isProtocolConfigExists(containerConfig, container)) {
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
m_serversModel->updateContainerConfig(container, containerConfig);
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig,
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
if (errorCode != ErrorCode::NoError) {
return errorCode;
if (protocolConfig.isEmpty()) {
return false;
}
}
return errorCode;
});
return true;
};
QEventLoop wait;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec();
ErrorCode errorCode = watcher.result();
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false;
if (isProtocolConfigExists(containerConfig, container)) {
emit configValidated(true);
return;
}
return true;
struct ValidationResult {
ErrorCode errorCode = ErrorCode::NoError;
QJsonObject containerConfig;
};
QFuture<ValidationResult> future =
QtConcurrent::run([settings = m_settings, serverController, credentials, containerConfig, container]() mutable {
ValidationResult result;
result.containerConfig = containerConfig;
VpnConfigurationsController vpnConfigurationController(settings, serverController);
result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container,
result.containerConfig);
return result;
});
auto *watcher = new QFutureWatcher<ValidationResult>(this);
connect(watcher, &QFutureWatcher<ValidationResult>::finished, this,
[this, watcher, container, credentials, serverController]() {
auto result = watcher->result();
watcher->deleteLater();
if (result.errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(result.errorCode);
emit configValidated(false);
return;
}
m_serversModel->updateContainerConfig(container, result.containerConfig);
ErrorCode appendError = m_clientManagementModel->appendClient(
container, credentials, result.containerConfig,
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
if (appendError != ErrorCode::NoError) {
emit installationErrorOccurred(appendError);
emit configValidated(false);
return;
}
emit configValidated(true);
});
watcher->setFuture(future);
}
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
@@ -1063,7 +1085,7 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (container == DockerContainer::Awg2) {
if (ContainerProps::isAwgContainer(container)) {
const AwgConfig oldConfig(oldProtoConfig);
const AwgConfig newConfig(newProtoConfig);

View File

@@ -50,9 +50,10 @@ public slots:
void addEmptyServer();
bool isConfigValid();
void validateConfig();
signals:
void configValidated(bool isValid);
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
void installServerFinished(const QString &finishMessage);

View File

@@ -60,7 +60,6 @@ namespace PageLoader
PageSetupWizardQrReader,
PageSetupWizardApiServicesList,
PageSetupWizardApiServiceInfo,
PageSetupWizardPremiumWebView,
PageProtocolOpenVpnSettings,
PageProtocolShadowSocksSettings,

View File

@@ -178,12 +178,11 @@ void SettingsController::backupAppConfig(const QString &fileName)
void SettingsController::restoreAppConfig(const QString &fileName)
{
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
QByteArray data;
if (!SystemController::readFile(fileName, data)) {
emit changeSettingsErrorOccurred(tr("Can't open file: %1").arg(fileName));
return;
}
restoreAppConfigFromData(data);
}
@@ -308,6 +307,15 @@ void SettingsController::toggleStartMinimized(bool enable)
emit startMinimizedChanged();
}
bool SettingsController::isNewsNotificationsEnabled()
{
return m_settings->isNewsNotifications();
}
void SettingsController::toggleNewsNotificationsEnabled(bool enable)
{
m_settings->setNewsNotifications(enable);
}
bool SettingsController::isScreenshotsEnabled()
{
return m_settings->isScreenshotsEnabled();

View File

@@ -73,6 +73,9 @@ public slots:
bool isStartMinimizedEnabled();
void toggleStartMinimized(bool enable);
bool isNewsNotificationsEnabled();
void toggleNewsNotificationsEnabled(bool enable);
bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable);

View File

@@ -7,10 +7,8 @@
#include "systemController.h"
#include "core/networkUtilities.h"
SitesController::SitesController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<VpnConnection> &vpnConnection,
const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel)
SitesController::SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
: QObject(parent), m_settings(settings), m_sitesModel(sitesModel)
{
}
@@ -34,32 +32,20 @@ void SitesController::addSite(QString hostname)
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
}
const auto &processSite = [this](const QString &hostname, const QString &ip) {
m_sitesModel->addSite(hostname, ip);
if (!ip.isEmpty()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << ip));
} else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname));
}
};
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
const auto &resolveCallback = [this](const QHostInfo &hostInfo) {
const QList<QHostAddress> &addresses = hostInfo.addresses();
for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
processSite(hostInfo.hostName(), addr.toString());
m_sitesModel->addSite(hostInfo.hostName(), addr.toString());
break;
}
}
};
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
processSite(hostname, "");
m_sitesModel->addSite(hostname, "");
} else {
processSite(hostname, "");
m_sitesModel->addSite(hostname, "");
QHostInfo::lookupHost(hostname, this, resolveCallback);
}
@@ -72,9 +58,6 @@ void SitesController::removeSite(int index)
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
m_sitesModel->removeSite(modelIndex);
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname));
emit finished(tr("Site removed: %1").arg(hostname));
}
@@ -128,8 +111,6 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
m_sitesModel->addSites(sites, replaceExisting);
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
emit finished(tr("Import completed"));
}

View File

@@ -11,9 +11,8 @@ class SitesController : public QObject
{
Q_OBJECT
public:
explicit SitesController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<VpnConnection> &vpnConnection,
const QSharedPointer<SitesModel> &sitesModel, QObject *parent = nullptr);
explicit SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel,
QObject *parent = nullptr);
public slots:
void addSite(QString hostname);
@@ -31,8 +30,6 @@ signals:
private:
std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<SitesModel> m_sitesModel;
};

View File

@@ -112,7 +112,11 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
if (price == "free") {
return tr("Free");
}
#if defined(Q_OS_IOS) || defined(MACOS_NE)
return tr("%1 $").arg(price);
#else
return tr("%1 $/month").arg(price);
#endif
}
case EndDateRole: {
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");

View File

@@ -141,10 +141,12 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
m_serverProtocolConfig[config_key::responsePacketJunkSize] =
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
if (protocolVersion == protocols::awg::awgV2) {
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
}
m_serverProtocolConfig[config_key::initPacketMagicHeader] =
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
m_serverProtocolConfig[config_key::responsePacketMagicHeader] =

View File

@@ -1,261 +0,0 @@
import QtQuick 2.2
import AmneziaWebView 1.0
import Style 1.0
Rectangle {
id: panel
property alias title: webView.title
property alias icon: webView.icon
property alias progress: webView.progress
property alias html: webView.html
property alias url: webView.url
property alias back: webView.back
property alias stop: webView.stop
property alias reload: webView.reload
property alias forward: webView.forward
property alias pressGrabTime: webView.pressGrabTime
property string onFailedUrl: ""
property string onFailedHtml: "<h2>" + qsTr("Can`t load page") + "</h2>"
property int preferredWidth: panel.width
property int preferredHeight: panel.height
property var scriptResult: undefined
property var requestResult: undefined
property var call_provider: undefined
property var call_data: undefined
property string status: "unknown" // success, failed
signal urlCalled(variant url)
signal alertCalled(variant message)
signal scriptCalled(string funName, variant args)
signal reloadCalled()
signal eventSended(variant event)
signal requestSended(variant request)
signal loadFinished()
signal loadFailed()
color: AmneziaStyle.color.onyxBlack
function call(funName, args) {
var script
if (panel.call_provider) {
panel.call_data = args
script = "call(" + funName + ")"
} else {
script = funName + "(" + JSON.stringify(args) + ")"
}
webView.evaluateJavaScript(script)
}
function event(e) {
call("receiveEvent", e)
}
function request(r) {
var req = r
req.result = call("receiveRequest", req)
return req
}
function newEvent(type) {
var e = {}
e.type = type
return e
}
function newRequest(type) {
var r = {}
r.type = type
r.result = null
return r
}
function toogleScale() {
webView.doToogleScale()
}
function zoomIn() {
webView.doZoomIn()
}
function zoomOut() {
webView.doZoomOut()
}
AmneziaWebView {
id: webView
anchors.fill: parent
property bool loaded: false
focus: true
smooth: false
backgroundColor: AmneziaStyle.color.onyxBlack
property int panelPreferredWidth: panel.preferredWidth
onPanelPreferredWidthChanged: {
webView.preferredWidth = panel.preferredWidth;
// if (webView.loaded)
// doZoomOrScale();
}
preferredWidth: panel.preferredWidth //webView.flexible ? panel.preferredWidth : Math.max(webView.contentsSize.width, 1024)
preferredHeight: panel.preferredHeight
contentsScale: 1
// onDoubleClick: {
// if (webView.flexible)
// return
// async.call(doToogleScale)
// }
function doToogleScale() {
// webView.needScale = !webView.needScale
// if (webView.needScale) {
// doScale()
// } else {
// webView.contentsScale = webView.zoomvalue
// }
}
function doZoomOrScale() {
// if (webView.needScale)
// doScale()
// else
// webView.contentsScale = webView.zoomvalue;
}
function doZoomIn() {
// if (webView.zoomvalue > 0.3) {
// webView.zoomvalue = webView.contentsScale
// webView.zoomvalue -= 0.1
// webView.contentsScale = webView.zoomvalue
// webView.needScale = false
// }
}
function doZoomOut() {
// if (webView.zoomvalue < 2.5) {
// webView.zoomvalue = webView.contentsScale
// webView.zoomvalue += 0.1
// webView.contentsScale = webView.zoomvalue
// webView.needScale = false
// }
}
function doScale() {
// var zoom = flickableItem.width / webView.preferredWidth
// webView.contentsScale = zoom;
}
pressGrabTime: 400
settings.defaultFontSize: 14
settings.standardFontFamily: "Arial"
//settings.developerExtrasEnabled: isDebugEnabled
onAlert: panel.alertCalled(message)
onUrlChanged: {
//flickableItem.contentX = 0
//flickableItem.contentY = 0
panel.urlCalled(url)
}
onLoadStarted: {
webView.loaded = false
//webView.contentsScale = 1
}
onLoadFinished: {
panel.status = "success"
webView.loaded = true
panel.loadFinished()
//async.call(doZoomOrScale)
}
onLoadFailed: {
console.debug("qml: html load failed: " + html)
if(html == ""){
if (panel.onFailedUrl !== "")
panel.url = panel.onFailedUrl;
else if (panel.onFailedHtml !== "")
panel.html = panel.onFailedHtml;
}
panel.status = "failed"
panel.loadFailed()
}
javaScriptWindowObjects: [
QtObject {
AmneziaWebView.windowObjectName: "script"
function call(functionName, args) {
panel.scriptCalled(functionName, args)
return scriptResult;
}
},
QtObject {
AmneziaWebView.windowObjectName: "send"
function event(e) {
panel.eventSended(e)
}
function request(r) {
var req = r
panel.requestSended(req)
req.result = requestResult
return req;
}
},
QtObject {
AmneziaWebView.windowObjectName: "log"
function error(msg){
webView.evaluateJavaScript("console.error('" + msg + "')")
return api.log.error(msg)
}
function warn(msg){
webView.evaluateJavaScript("console.warn('" + msg + "')")
return api.log.warn(msg)
}
function info(msg){
webView.evaluateJavaScript("console.info('" + msg + "')")
return api.log.info(msg)
}
function debug(msg){
webView.evaluateJavaScript("console.log('" + msg + "')")
return api.log.debug(msg)
}
function time(tag, msg){
webView.evaluateJavaScript("console.log('" + msg + "')")
return api.log.time(tag, msg)
}
},
QtObject {
AmneziaWebView.windowObjectName: "webView"
function scrollUp() {
//flickableItem.contentY = 0;
}
function reload() {
//panel.reloadCalled()
}
},
QtObject {
AmneziaWebView.windowObjectName: "call_provider"
function data(fn) {
return panel.call_provider ? panel.call_provider.data(fn, panel.call_data) : panel.call_data
}
}
]
}
}

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