mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-24 02:00:24 +07:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff589f47c2 | |||
| f0ca9772d3 | |||
| 23b530a0f8 | |||
| 511b8fa6cc | |||
| 38082f9940 | |||
| 71691fa01e | |||
| 62d9bcaf7f | |||
| 5ef8254cba | |||
| f767171c06 | |||
| eff460b227 | |||
| 319043818a | |||
| e730521576 | |||
| 517930dd22 | |||
| 26994c21b1 | |||
| 681eb5aa86 | |||
| 4b86425992 | |||
| 9b41ed66bb | |||
| 8bb4fa3f35 | |||
| e792117be1 |
@@ -44,13 +44,6 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
|
||||||
id: get_version
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
@@ -62,13 +55,13 @@ jobs:
|
|||||||
bash deploy/build_linux.sh
|
bash deploy/build_linux.sh
|
||||||
|
|
||||||
- name: 'Pack installer'
|
- name: 'Pack installer'
|
||||||
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
|
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
name: AmneziaVPN_Linux_installer.tar
|
||||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
path: deploy/AmneziaVPN_Linux_Installer.tar
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -109,14 +102,6 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
|
||||||
id: get_version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
@@ -147,16 +132,11 @@ jobs:
|
|||||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||||
call deploy\\build_windows.bat
|
call deploy\\build_windows.bat
|
||||||
|
|
||||||
- name: 'Rename Windows installer'
|
|
||||||
shell: cmd
|
|
||||||
run: |
|
|
||||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
|
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
name: AmneziaVPN_Windows_installer
|
||||||
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -351,7 +331,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.8.3
|
QT_VERSION: 6.8.0
|
||||||
|
|
||||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||||
|
|
||||||
@@ -381,7 +361,7 @@ jobs:
|
|||||||
xcode-version: '16.2.0'
|
xcode-version: '16.2.0'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'mac'
|
host: 'mac'
|
||||||
@@ -389,15 +369,10 @@ jobs:
|
|||||||
arch: 'clang_64'
|
arch: 'clang_64'
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
#setup-python: 'true'
|
|
||||||
#set-env: 'true'
|
|
||||||
#extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
aqtversion: '==3.3.0'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
py7zrversion: '==0.22.*'
|
|
||||||
extra: '--base ${{ env.QT_MIRROR }}'
|
|
||||||
cache: 'true'
|
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -405,13 +380,6 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
|
||||||
id: get_version
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
@@ -420,17 +388,11 @@ jobs:
|
|||||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
bash deploy/build_macos.sh -n
|
bash deploy/build_macos.sh -n
|
||||||
|
|
||||||
- name: 'Pack macOS installer'
|
|
||||||
run: |
|
|
||||||
cd deploy/build/pkg
|
|
||||||
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
|
|
||||||
cd ../../..
|
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
name: AmneziaVPN_MacOS_installer
|
||||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -507,8 +469,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-36
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.8.3
|
QT_VERSION: 6.7.3
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
@@ -589,13 +551,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
|
||||||
id: get_version
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
@@ -629,44 +584,35 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
||||||
|
|
||||||
- name: 'Rename Android APKs'
|
|
||||||
run: |
|
|
||||||
cd deploy/build
|
|
||||||
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
|
|
||||||
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
|
|
||||||
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
|
|
||||||
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
- name: 'Upload x86_64 apk'
|
- name: 'Upload x86_64 apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
name: AmneziaVPN-android-x86_64
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload x86 apk'
|
- name: 'Upload x86 apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
name: AmneziaVPN-android-x86
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
path: deploy/build/AmneziaVPN-x86-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload arm64-v8a apk'
|
- name: 'Upload arm64-v8a apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
name: AmneziaVPN-android-arm64-v8a
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload armeabi-v7a apk'
|
- name: 'Upload armeabi-v7a apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
name: AmneziaVPN-android-armeabi-v7a
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ deploy/build_32/*
|
|||||||
deploy/build_64/*
|
deploy/build_64/*
|
||||||
winbuild*.bat
|
winbuild*.bat
|
||||||
.cache/
|
.cache/
|
||||||
.vscode/
|
|
||||||
|
|
||||||
|
|
||||||
# Qt-es
|
# Qt-es
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.11.5)
|
set(AMNEZIAVPN_VERSION 4.8.11.0)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2100)
|
set(APP_ANDROID_VERSION_CODE 2095)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
+1
-1
Submodule client/3rd-prebuilt updated: 02a00ee00d...efad1a5b5c
Vendored
+1
-1
Submodule client/3rd/amneziawg-apple updated: 811af0a83b...76e7db556a
@@ -56,7 +56,7 @@ target_include_directories(${PROJECT} PUBLIC
|
|||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) 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_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_interface.rep)
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||||
@@ -76,7 +76,6 @@ set(AMNEZIAVPN_TS_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_es_ES.ts
|
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QDir>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/controllers/pageController.h"
|
#include "ui/controllers/pageController.h"
|
||||||
@@ -55,44 +53,16 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
|||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
{
|
{
|
||||||
if (m_vpnConnection) {
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
|
|
||||||
QThread::msleep(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_vpnConnectionThread.requestInterruption();
|
|
||||||
m_vpnConnectionThread.quit();
|
m_vpnConnectionThread.quit();
|
||||||
|
|
||||||
if (!m_vpnConnectionThread.wait(3000)) {
|
|
||||||
m_vpnConnectionThread.terminate();
|
|
||||||
m_vpnConnectionThread.wait(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_engine) {
|
if (m_engine) {
|
||||||
QObject::disconnect(m_engine, 0, 0, 0);
|
QObject::disconnect(m_engine, 0, 0, 0);
|
||||||
delete m_engine;
|
delete m_engine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
namespace {
|
|
||||||
static void clearQtCaches()
|
|
||||||
{
|
|
||||||
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
|
|
||||||
if (!cacheRoot.isEmpty()) {
|
|
||||||
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
|
|
||||||
QDir(cacheRoot + "/qmlcache").removeRecursively();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void AmneziaApplication::init()
|
void AmneziaApplication::init()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
clearQtCaches();
|
|
||||||
#endif
|
|
||||||
m_engine = new QQmlApplicationEngine;
|
m_engine = new QQmlApplicationEngine;
|
||||||
|
|
||||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||||
|
|||||||
@@ -45,8 +45,7 @@
|
|||||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:windowSoftInputMode="adjustResize|stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||||
android:enableOnBackInvokedCallback="false"
|
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -215,4 +214,4 @@
|
|||||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
<item name="android:colorBackground">@color/black</item>
|
<item name="android:colorBackground">@color/black</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
||||||
<item name="android:enforceNavigationBarContrast">false</item>
|
|
||||||
<item name="android:enforceStatusBarContrast">false</item>
|
|
||||||
</style>
|
</style>
|
||||||
<style name="Translucent" parent="NoActionBar">
|
<style name="Translucent" parent="NoActionBar">
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
|||||||
@@ -35,11 +35,6 @@ import android.widget.Toast
|
|||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.Insets
|
|
||||||
import androidx.core.view.OnApplyWindowInsetsListener
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@@ -175,9 +170,10 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity")
|
Log.d(TAG, "Create Amnezia activity")
|
||||||
loadLibs()
|
loadLibs()
|
||||||
|
window.apply {
|
||||||
// Configure window for edge-to-edge display
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
configureWindowForEdgeToEdge()
|
statusBarColor = getColor(R.color.black)
|
||||||
|
}
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
val proto = mainScope.async(Dispatchers.IO) {
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
VpnStateStore.getVpnState().vpnProto
|
VpnStateStore.getVpnState().vpnProto
|
||||||
@@ -269,82 +265,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
window.decorView.apply {
|
|
||||||
invalidate()
|
|
||||||
|
|
||||||
postDelayed({
|
|
||||||
sendTouch(1f, 1f)
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
postDelayed({
|
|
||||||
sendTouch(2f, 2f)
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
postDelayed({
|
|
||||||
requestLayout()
|
|
||||||
invalidate()
|
|
||||||
}, 250)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun configureWindowForEdgeToEdge() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
window.apply {
|
|
||||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
|
||||||
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
|
|
||||||
statusBarColor = android.graphics.Color.TRANSPARENT
|
|
||||||
navigationBarColor = android.graphics.Color.TRANSPARENT
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
|
||||||
isAppearanceLightStatusBars = false
|
|
||||||
isAppearanceLightNavigationBars = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for Android 14 (API 34+) IME adjustResize bug
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
setupImeInsetsListener()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.apply {
|
|
||||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
|
||||||
statusBarColor = getColor(R.color.black)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupImeInsetsListener() {
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
|
||||||
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
|
||||||
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
|
||||||
|
|
||||||
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
|
||||||
|
|
||||||
val density = resources.displayMetrics.density
|
|
||||||
val imeHeightDp = (imeHeight / density).toInt()
|
|
||||||
|
|
||||||
// Also track system bars (navigation bar, status bar) changes
|
|
||||||
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
val navBarHeight = systemBarsInsets.bottom
|
|
||||||
val navBarHeightDp = (navBarHeight / density).toInt()
|
|
||||||
val statusBarHeight = systemBarsInsets.top
|
|
||||||
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
|
||||||
|
|
||||||
mainScope.launch {
|
|
||||||
qtInitialized.await()
|
|
||||||
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
|
||||||
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return windowInsets instead of CONSUMED to allow proper handling
|
|
||||||
windowInsets
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Destroy Amnezia activity")
|
Log.d(TAG, "Destroy Amnezia activity")
|
||||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
@@ -746,43 +666,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun getStatusBarHeight(): Int {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
|
||||||
|
|
||||||
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
|
||||||
val heightPx = if (resourceId > 0) {
|
|
||||||
resources.getDimensionPixelSize(resourceId)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert physical pixels to device-independent pixels for QML
|
|
||||||
val density = resources.displayMetrics.density
|
|
||||||
val heightDp = (heightPx / density).toInt()
|
|
||||||
return heightDp
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
fun getNavigationBarHeight(): Int {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
|
|
||||||
|
|
||||||
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
|
||||||
val heightPx = if (resourceId > 0) {
|
|
||||||
resources.getDimensionPixelSize(resourceId)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert physical pixels to device-independent pixels for QML
|
|
||||||
val density = resources.displayMetrics.density
|
|
||||||
val heightDp = (heightPx / density).toInt()
|
|
||||||
return heightDp
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun startQrCodeReader() {
|
fun startQrCodeReader() {
|
||||||
Log.v(TAG, "Start camera")
|
Log.v(TAG, "Start camera")
|
||||||
|
|||||||
@@ -38,15 +38,15 @@ object AppListProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
|
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
||||||
val name: String?
|
val name: String?
|
||||||
val packageName: String = pi.packageName
|
val packageName: String = pi.packageName
|
||||||
val icon: Boolean = (ai?.icon ?: 0) != 0
|
val icon: Boolean = ai.icon != 0
|
||||||
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val name = ai?.loadLabel(pm)?.toString()
|
val name = ai.loadLabel(pm).toString()
|
||||||
this.name = name?.takeIf { it != packageName }
|
this.name = if (name != packageName) name else null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: App): Int {
|
override fun compareTo(other: App): Int {
|
||||||
|
|||||||
@@ -28,7 +28,4 @@ object QtAndroidController {
|
|||||||
external fun onAuthResult(result: Boolean)
|
external fun onAuthResult(result: Boolean)
|
||||||
|
|
||||||
external fun decodeQrCode(data: String): Boolean
|
external fun decodeQrCode(data: String): Boolean
|
||||||
|
|
||||||
external fun onImeInsetsChanged(heightDp: Int)
|
|
||||||
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
||||||
|
|
||||||
set(APP_ANDROID_MIN_SDK 28)
|
set(APP_ANDROID_MIN_SDK 26)
|
||||||
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||||
"The minimum API level supported by the application or library" FORCE)
|
"The minimum API level supported by the application or library" FORCE)
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
|
|||||||
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
|
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
|
||||||
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
||||||
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
||||||
QT_ANDROID_TARGET_SDK_VERSION 36
|
QT_ANDROID_TARGET_SDK_VERSION 34
|
||||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
|
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
||||||
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ set(HEADERS ${HEADERS}
|
|||||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -79,7 +78,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
@@ -175,7 +173,7 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
message("Client desktop build")
|
message("Client desktop build")
|
||||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
|
|
||||||
@@ -205,14 +203,3 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
|||||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE AND MACOS_NE)
|
|
||||||
# Include only the tray notification handler in NE builds
|
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -83,30 +83,12 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sanitizeStaticKey = [](const QString &key) {
|
|
||||||
QStringList lines = key.split('\n');
|
|
||||||
QStringList filtered;
|
|
||||||
filtered.reserve(lines.size());
|
|
||||||
for (const QString &line : lines) {
|
|
||||||
const QString trimmed = line.trimmed();
|
|
||||||
if (trimmed.startsWith('#')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
filtered.append(line);
|
|
||||||
}
|
|
||||||
QString result = filtered.join('\n');
|
|
||||||
if (!result.endsWith('\n')) {
|
|
||||||
result.append('\n');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
config.replace("$OPENVPN_CA_CERT", connData.caCert);
|
||||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||||
|
|
||||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||||
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||||
} else {
|
} else {
|
||||||
config.replace("<tls-auth>", "");
|
config.replace("<tls-auth>", "");
|
||||||
config.replace("</tls-auth>", "");
|
config.replace("</tls-auth>", "");
|
||||||
@@ -135,7 +117,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
|||||||
if (!isApiConfig) {
|
if (!isApiConfig) {
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||||
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||||
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||||
|
|||||||
@@ -47,14 +47,12 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String serverCountryName("server_country_name");
|
constexpr QLatin1String serverCountryName("server_country_name");
|
||||||
|
|
||||||
constexpr QLatin1String osVersion("os_version");
|
constexpr QLatin1String osVersion("os_version");
|
||||||
constexpr QLatin1String appLanguage("app_language");
|
|
||||||
|
|
||||||
constexpr QLatin1String availableCountries("available_countries");
|
constexpr QLatin1String availableCountries("available_countries");
|
||||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||||
constexpr QLatin1String subscriptionDescription("subscription_description");
|
|
||||||
|
|
||||||
constexpr QLatin1String supportInfo("support_info");
|
constexpr QLatin1String supportInfo("support_info");
|
||||||
constexpr QLatin1String email("email");
|
constexpr QLatin1String email("email");
|
||||||
@@ -66,16 +64,6 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String id("id");
|
constexpr QLatin1String id("id");
|
||||||
constexpr QLatin1String orderId("order_id");
|
constexpr QLatin1String orderId("order_id");
|
||||||
constexpr QLatin1String migrationCode("migration_code");
|
constexpr QLatin1String migrationCode("migration_code");
|
||||||
|
|
||||||
constexpr QLatin1String transactionId("transaction_id");
|
|
||||||
|
|
||||||
constexpr QLatin1String userCountryCode("user_country_code");
|
|
||||||
|
|
||||||
constexpr QLatin1String serviceInfo("service_info");
|
|
||||||
constexpr QLatin1String isAdVisible("is_ad_visible");
|
|
||||||
constexpr QLatin1String adHeader("ad_header");
|
|
||||||
constexpr QLatin1String adDescription("ad_description");
|
|
||||||
constexpr QLatin1String adEndpoint("ad_endpoint");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
|||||||
@@ -82,9 +82,7 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
|
|||||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
|
||||||
const QByteArray &responseBody)
|
|
||||||
{
|
{
|
||||||
const int httpStatusCodeConflict = 409;
|
const int httpStatusCodeConflict = 409;
|
||||||
const int httpStatusCodeNotFound = 404;
|
const int httpStatusCodeNotFound = 404;
|
||||||
@@ -92,19 +90,21 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
if (!sslErrors.empty()) {
|
if (!sslErrors.empty()) {
|
||||||
qDebug().noquote() << sslErrors;
|
qDebug().noquote() << sslErrors;
|
||||||
return amnezia::ErrorCode::ApiConfigSslError;
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
} else if (replyError == QNetworkReply::NoError) {
|
} else if (reply->error() == QNetworkReply::NoError) {
|
||||||
return amnezia::ErrorCode::NoError;
|
return amnezia::ErrorCode::NoError;
|
||||||
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << QString::fromUtf8(responseBody);
|
QString err = reply->errorString();
|
||||||
qDebug() << replyError;
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
qDebug() << replyErrorString;
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
qDebug() << httpStatusCode;
|
qDebug() << httpStatusCode;
|
||||||
if (httpStatusCode == httpStatusCodeConflict) {
|
if (httpStatusCode == httpStatusCodeConflict) {
|
||||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
@@ -162,51 +162,3 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
|||||||
|
|
||||||
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
|
||||||
{
|
|
||||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString vpnKeyText = "";
|
|
||||||
|
|
||||||
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
|
||||||
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
|
|
||||||
|
|
||||||
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
|
|
||||||
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
|
|
||||||
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
|
|
||||||
|
|
||||||
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
|
|
||||||
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
|
|
||||||
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
|
|
||||||
|
|
||||||
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
|
|
||||||
|
|
||||||
QString vpnKeyStr = "{";
|
|
||||||
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
|
|
||||||
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
|
|
||||||
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
|
|
||||||
|
|
||||||
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
|
|
||||||
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
|
|
||||||
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
|
|
||||||
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
|
|
||||||
vpnKeyStr += "}, ";
|
|
||||||
|
|
||||||
vpnKeyStr += "\"auth_data\": {";
|
|
||||||
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
|
|
||||||
vpnKeyStr += "}";
|
|
||||||
|
|
||||||
vpnKeyStr += "}";
|
|
||||||
|
|
||||||
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
|
||||||
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
|
||||||
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
|
||||||
|
|
||||||
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
|
||||||
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
|
||||||
|
|
||||||
return vpnKeyText;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,12 +18,9 @@ namespace apiUtils
|
|||||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
|
||||||
const QByteArray &responseBody);
|
|
||||||
|
|
||||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||||
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // APIUTILS_H
|
#endif // APIUTILS_H
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ void CoreController::initModels()
|
|||||||
|
|
||||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||||
|
|
||||||
m_newsModel.reset(new NewsModel(m_settings, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initControllers()
|
void CoreController::initControllers()
|
||||||
@@ -156,9 +153,6 @@ void CoreController::initControllers()
|
|||||||
|
|
||||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||||
|
|
||||||
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
@@ -322,11 +316,6 @@ void CoreController::initContainerModelUpdateHandler()
|
|||||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
&ContainersModel::updateModel);
|
&ContainersModel::updateModel);
|
||||||
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
|
|
||||||
if (m_serversModel->hasServersFromGatewayApi()) {
|
|
||||||
m_apiNewsController->fetchNews(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
m_serversModel->resetModel();
|
m_serversModel->resetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||||
#include "ui/controllers/api/apiNewsController.h"
|
|
||||||
#include "ui/controllers/appSplitTunnelingController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
#include "ui/controllers/allowedDnsController.h"
|
#include "ui/controllers/allowedDnsController.h"
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
@@ -48,7 +47,6 @@
|
|||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/newsModel.h"
|
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
#include "ui/notificationhandler.h"
|
#include "ui/notificationhandler.h"
|
||||||
@@ -120,7 +118,6 @@ private:
|
|||||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||||
QScopedPointer<ApiNewsController> m_apiNewsController;
|
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
@@ -128,7 +125,6 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
QSharedPointer<NewsModel> m_newsModel;
|
|
||||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
#include "gatewayController.h"
|
#include "gatewayController.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QPromise>
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
@@ -53,25 +50,87 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
||||||
{
|
{
|
||||||
EncryptedRequestData encRequestData;
|
|
||||||
encRequestData.errorCode = ErrorCode::NoError;
|
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->requestInetAccess();
|
IosController::Instance()->requestInetAccess();
|
||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
QNetworkRequest request;
|
||||||
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
|
|
||||||
|
request.setUrl(QString(endpoint).arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
// bypass killSwitch exceptions for API-gateway
|
// bypass killSwitch exceptions for API-gateway
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
if (m_isStrictKillSwitchEnabled) {
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
QString host = QUrl(encRequestData.request.url()).host();
|
QString host = QUrl(request.url()).host();
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
|
if (!ip.isEmpty()) {
|
||||||
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkReply *reply;
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
responseBody = reply->readAll();
|
||||||
|
|
||||||
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||||
|
auto requestFunction = [&request, &responseBody](const QString &url) {
|
||||||
|
request.setUrl(url);
|
||||||
|
return amnApp->networkManager()->get(request);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
||||||
|
const QList<QSslError> &nestedSslErrors) {
|
||||||
|
responseBody = nestedReply->readAll();
|
||||||
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
||||||
|
sslErrors = nestedSslErrors;
|
||||||
|
reply = nestedReply;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
|
|
||||||
|
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
|
// bypass killSwitch exceptions for API-gateway
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
|
QString host = QUrl(request.url()).host();
|
||||||
QString ip = NetworkUtilities::getIPAddress(host);
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
if (!ip.isEmpty()) {
|
if (!ip.isEmpty()) {
|
||||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
@@ -80,14 +139,14 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
QJsonObject keyPayload;
|
QJsonObject keyPayload;
|
||||||
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||||
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||||
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||||
|
|
||||||
QByteArray encryptedKeyPayload;
|
QByteArray encryptedKeyPayload;
|
||||||
QByteArray encryptedApiPayload;
|
QByteArray encryptedApiPayload;
|
||||||
@@ -102,88 +161,62 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
|||||||
} catch (...) {
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error loading public key from environment variables";
|
qCritical() << "error loading public key from environment variables";
|
||||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
return ErrorCode::ApiMissingAgwPublicKey;
|
||||||
return encRequestData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
EVP_PKEY_free(publicKey);
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||||
"", encRequestData.salt);
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error when encrypting the request body";
|
qCritical() << "error when encrypting the request body";
|
||||||
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
return encRequestData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestBody;
|
QJsonObject requestBody;
|
||||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
return encRequestData;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
|
||||||
{
|
|
||||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
|
||||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
|
||||||
return encRequestData.errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
|
||||||
|
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
wait.exec();
|
||||||
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
QString replyErrorString = reply->errorString();
|
|
||||||
auto replyError = reply->error();
|
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
reply->deleteLater();
|
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
|
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||||
if (sslErrors.isEmpty()
|
request.setUrl(url);
|
||||||
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
|
||||||
encRequestData.request.setUrl(url);
|
|
||||||
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors,
|
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||||
&encRequestData, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = reply->readAll();
|
encryptedResponseBody = nestedReply->readAll();
|
||||||
replyErrorString = reply->errorString();
|
reply = nestedReply;
|
||||||
replyError = reply->error();
|
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||||
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
if (!sslErrors.isEmpty()
|
|
||||||
|| shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
|
||||||
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
reply->deleteLater();
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
responseBody =
|
|
||||||
blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
|
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
@@ -192,94 +225,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
QStringList GatewayController::getProxyUrls()
|
||||||
{
|
|
||||||
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
|
||||||
promise->start();
|
|
||||||
|
|
||||||
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
|
||||||
if (encRequestData.errorCode != ErrorCode::NoError) {
|
|
||||||
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
|
||||||
promise->finish();
|
|
||||||
return promise->future();
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
|
||||||
|
|
||||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
|
||||||
QString replyErrorString = reply->errorString();
|
|
||||||
auto replyError = reply->error();
|
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
auto processResponse = [promise, encRequestData](const QByteArray &ecryptedResponseBody, const QList<QSslError> &sslErrors,
|
|
||||||
QNetworkReply::NetworkError replyError, const QString &replyErrorString,
|
|
||||||
int httpStatusCode) {
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, ecryptedResponseBody);
|
|
||||||
if (errorCode) {
|
|
||||||
promise->addResult(qMakePair(errorCode, QByteArray()));
|
|
||||||
promise->finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
|
||||||
try {
|
|
||||||
QByteArray responseBody = blockCipher.decryptAesBlockCipher(ecryptedResponseBody, encRequestData.key, encRequestData.iv, "",
|
|
||||||
encRequestData.salt);
|
|
||||||
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
|
|
||||||
promise->finish();
|
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
|
||||||
qCritical() << "error when decrypting the request body";
|
|
||||||
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
|
||||||
promise->finish();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sslErrors->isEmpty()
|
|
||||||
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
|
|
||||||
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
|
||||||
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
|
||||||
|
|
||||||
QStringList baseUrls;
|
|
||||||
if (m_isDevEnvironment) {
|
|
||||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
|
||||||
} else {
|
|
||||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList proxyStorageUrls;
|
|
||||||
if (!serviceType.isEmpty()) {
|
|
||||||
for (const auto &baseUrl : baseUrls) {
|
|
||||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
|
||||||
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
|
|
||||||
+ ".json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto &baseUrl : baseUrls)
|
|
||||||
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
|
||||||
|
|
||||||
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
|
||||||
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrls) {
|
|
||||||
bypassProxyAsync(endpoint, proxyUrls, encRequestData, processResponse);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
processResponse(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise->future();
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
|
||||||
{
|
{
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
@@ -289,33 +235,22 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
QNetworkReply *reply;
|
QNetworkReply *reply;
|
||||||
|
|
||||||
QStringList baseUrls;
|
QStringList proxyStorageUrls;
|
||||||
if (m_isDevEnvironment) {
|
if (m_isDevEnvironment) {
|
||||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||||
} else {
|
} else {
|
||||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
QStringList proxyStorageUrls;
|
|
||||||
if (!serviceType.isEmpty()) {
|
|
||||||
for (const auto &baseUrl : baseUrls) {
|
|
||||||
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
|
||||||
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto &baseUrl : baseUrls) {
|
|
||||||
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
request.setUrl(proxyStorageUrl);
|
request.setUrl(proxyStorageUrl);
|
||||||
reply = amnApp->networkManager()->get(request);
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
wait.exec();
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
auto encryptedResponseBody = reply->readAll();
|
auto encryptedResponseBody = reply->readAll();
|
||||||
@@ -353,10 +288,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
}
|
}
|
||||||
return endpoints;
|
return endpoints;
|
||||||
} else {
|
} else {
|
||||||
auto replyError = reply->error();
|
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
qDebug() << replyError;
|
|
||||||
qDebug() << httpStatusCode;
|
|
||||||
qDebug() << "go to the next storage endpoint";
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
@@ -365,33 +297,33 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody,
|
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||||
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt)
|
const QByteArray &iv, const QByteArray &salt)
|
||||||
{
|
{
|
||||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
qDebug() << "timeout occurred";
|
qDebug() << "timeout occurred";
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return true;
|
return true;
|
||||||
} else if (responseBody.contains("html")) {
|
} else if (responseBody.contains("html")) {
|
||||||
qDebug() << "the response contains an html tag";
|
qDebug() << "the response contains an html tag";
|
||||||
return true;
|
return true;
|
||||||
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) {
|
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||||
|| responseBody.contains(errorResponsePattern3)) {
|
|| responseBody.contains(errorResponsePattern3)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (replyError != QNetworkReply::NetworkError::NoError) {
|
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return true;
|
return true;
|
||||||
} else if (checkEncryption) {
|
} else if (checkEncryption) {
|
||||||
try {
|
try {
|
||||||
@@ -405,33 +337,35 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
||||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||||
{
|
{
|
||||||
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
|
QStringList proxyUrls = getProxyUrls();
|
||||||
std::random_device randomDevice;
|
std::random_device randomDevice;
|
||||||
std::mt19937 generator(randomDevice());
|
std::mt19937 generator(randomDevice());
|
||||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
|
|
||||||
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
|
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl, QNetworkReply *reply,
|
||||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
|
|
||||||
qDebug() << "go to the next proxy endpoint";
|
qDebug() << "go to the next proxy endpoint";
|
||||||
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
|
reply->deleteLater(); // delete the previous reply
|
||||||
|
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
wait.exec();
|
||||||
|
|
||||||
auto result = replyProcessingFunction(reply, sslErrors);
|
if (replyProcessingFunction(reply, sslErrors)) {
|
||||||
reply->deleteLater();
|
return true;
|
||||||
return result;
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (m_proxyUrl.isEmpty()) {
|
if (m_proxyUrl.isEmpty()) {
|
||||||
@@ -449,7 +383,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
|||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
wait.exec();
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
@@ -465,146 +399,15 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!m_proxyUrl.isEmpty()) {
|
if (!m_proxyUrl.isEmpty()) {
|
||||||
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
|
if (bypassFunction(endpoint, m_proxyUrl, reply, requestFunction, replyProcessingFunction)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString &proxyUrl : proxyUrls) {
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
|
if (bypassFunction(endpoint, proxyUrl, reply, requestFunction, replyProcessingFunction)) {
|
||||||
m_proxyUrl = proxyUrl;
|
m_proxyUrl = proxyUrl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
|
||||||
std::function<void(const QStringList &)> onComplete)
|
|
||||||
{
|
|
||||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
|
||||||
onComplete({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
|
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
|
||||||
|
|
||||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
QByteArray encrypted = reply->readAll();
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
|
||||||
try {
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
|
||||||
if (!m_isDevEnvironment) {
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
|
||||||
hash.addData(key);
|
|
||||||
QByteArray h = hash.result().toHex();
|
|
||||||
|
|
||||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
|
||||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
|
||||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher cipher;
|
|
||||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
|
||||||
} else {
|
|
||||||
responseBody = encrypted;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
|
||||||
qCritical() << "error decrypting payload";
|
|
||||||
QMetaObject::invokeMethod(
|
|
||||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
|
||||||
QStringList endpoints;
|
|
||||||
for (const QJsonValue &endpoint : endpointsArray)
|
|
||||||
endpoints.push_back(endpoint.toString());
|
|
||||||
|
|
||||||
QStringList shuffled = endpoints;
|
|
||||||
std::random_device randomDevice;
|
|
||||||
std::mt19937 generator(randomDevice());
|
|
||||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
|
||||||
|
|
||||||
onComplete(shuffled);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
qDebug() << httpStatusCode;
|
|
||||||
qDebug() << "go to the next storage endpoint";
|
|
||||||
reply->deleteLater();
|
|
||||||
QMetaObject::invokeMethod(
|
|
||||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete)
|
|
||||||
{
|
|
||||||
if (currentProxyIndex >= proxyUrls.size()) {
|
|
||||||
onComplete("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setTransferTimeout(1000);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
|
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
|
||||||
|
|
||||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
|
|
||||||
// *(state->sslErrors) = e;
|
|
||||||
// });
|
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
m_proxyUrl = proxyUrls[currentProxyIndex];
|
|
||||||
onComplete(m_proxyUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "go to the next proxy endpoint";
|
|
||||||
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GatewayController::bypassProxyAsync(
|
|
||||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
|
||||||
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
|
|
||||||
{
|
|
||||||
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
|
||||||
if (proxyUrl.isEmpty()) {
|
|
||||||
onComplete(QByteArray(), *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QNetworkRequest request = encRequestData.request;
|
|
||||||
request.setUrl(endpoint.arg(proxyUrl));
|
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
|
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, reply]() {
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
|
||||||
QString replyErrorString = reply->errorString();
|
|
||||||
auto replyError = reply->error();
|
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
onComplete(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
#ifndef GATEWAYCONTROLLER_H
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
#define GATEWAYCONTROLLER_H
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
#include <QFuture>
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPair>
|
|
||||||
#include <QPromise>
|
|
||||||
#include <QSharedPointer>
|
|
||||||
|
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
@@ -22,36 +18,16 @@ public:
|
|||||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct EncryptedRequestData
|
QStringList getProxyUrls();
|
||||||
{
|
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||||
QNetworkRequest request;
|
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
QByteArray requestBody;
|
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
QByteArray key;
|
|
||||||
QByteArray iv;
|
|
||||||
QByteArray salt;
|
|
||||||
amnezia::ErrorCode errorCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
|
||||||
|
|
||||||
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
|
||||||
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
|
|
||||||
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
|
|
||||||
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
|
||||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
|
||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
|
|
||||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
|
||||||
std::function<void(const QStringList &)> onComplete);
|
|
||||||
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
|
||||||
void bypassProxyAsync(
|
|
||||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
|
||||||
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
|
|
||||||
|
|
||||||
int m_requestTimeoutMsecs;
|
int m_requestTimeoutMsecs;
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevEnvironment = false;
|
bool m_isDevEnvironment = false;
|
||||||
|
|||||||
@@ -197,12 +197,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
QTemporaryFile localFile;
|
// Write directly via SCP without creating a temporary local file.
|
||||||
localFile.open();
|
error = m_sshClient.scpWriteBuffer(overwriteMode, data, remotePath, "non_desc");
|
||||||
localFile.write(data);
|
|
||||||
localFile.close();
|
|
||||||
|
|
||||||
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
|
|
||||||
|
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
return error;
|
return error;
|
||||||
|
|||||||
@@ -18,20 +18,10 @@ bool IpcClient::isSocketConnected() const
|
|||||||
return m_isSocketConnected;
|
return m_isSocketConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IpcClient::closeAndResetInstance(bool deleteSelf)
|
void IpcClient::close()
|
||||||
{
|
{
|
||||||
if (m_localSocket)
|
if (m_localSocket)
|
||||||
{
|
m_localSocket->close();
|
||||||
m_localSocket->disconnectFromServer();
|
|
||||||
m_localSocket->deleteLater();
|
|
||||||
m_localSocket.clear();
|
|
||||||
}
|
|
||||||
m_ipcClient.reset();
|
|
||||||
m_Tun2SocksClient.reset();
|
|
||||||
m_isSocketConnected = false;
|
|
||||||
if (deleteSelf) {
|
|
||||||
m_instance = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcClient *IpcClient::Instance()
|
IpcClient *IpcClient::Instance()
|
||||||
@@ -55,10 +45,6 @@ QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
|||||||
|
|
||||||
bool IpcClient::init(IpcClient *instance)
|
bool IpcClient::init(IpcClient *instance)
|
||||||
{
|
{
|
||||||
if (m_instance && m_instance != instance) {
|
|
||||||
m_instance->closeAndResetInstance(false);
|
|
||||||
m_instance->deleteLater();
|
|
||||||
}
|
|
||||||
m_instance = instance;
|
m_instance = instance;
|
||||||
|
|
||||||
Instance()->m_localSocket = new QLocalSocket(Instance());
|
Instance()->m_localSocket = new QLocalSocket(Instance());
|
||||||
@@ -105,9 +91,8 @@ bool IpcClient::init(IpcClient *instance)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "IpcClient::init succeed";
|
qDebug() << "IpcClient::init succeed";
|
||||||
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
|
||||||
|
|
||||||
return Instance()->isSocketConnected();
|
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public:
|
|||||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||||
|
|
||||||
bool isSocketConnected() const;
|
bool isSocketConnected() const;
|
||||||
void closeAndResetInstance(bool deleteSelf = false);
|
void close();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
|||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
qDebug() << "Getting Current Internet Adapter that routes to"
|
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||||
<< dst.toString();
|
<< dst.toString();
|
||||||
quint32 ipBigEndian;
|
quint32_be ipBigEndian;
|
||||||
quint32 ip = dst.toIPv4Address();
|
quint32 ip = dst.toIPv4Address();
|
||||||
qToBigEndian(ip, &ipBigEndian);
|
qToBigEndian(ip, &ipBigEndian);
|
||||||
_MIB_IPFORWARDROW routeInfo;
|
_MIB_IPFORWARDROW routeInfo;
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
#include "osSignalHandler.h"
|
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QSocketNotifier>
|
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <sys/signalfd.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#elif defined(Q_OS_MACOS)
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#include <QMetaObject>
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
|
|
||||||
static bool initialized = false;
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
static BOOL WINAPI consoleHandler(DWORD signal)
|
|
||||||
{
|
|
||||||
switch (signal) {
|
|
||||||
case CTRL_CLOSE_EVENT:
|
|
||||||
case CTRL_C_EVENT:
|
|
||||||
case CTRL_BREAK_EVENT:
|
|
||||||
case CTRL_LOGOFF_EVENT:
|
|
||||||
case CTRL_SHUTDOWN_EVENT:
|
|
||||||
if (QCoreApplication::instance()) {
|
|
||||||
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
default: return FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
|
||||||
static int signalFd = -1;
|
|
||||||
static QSocketNotifier *socketNotifier = nullptr;
|
|
||||||
|
|
||||||
static void setupUnixSignalHandler()
|
|
||||||
{
|
|
||||||
sigset_t set;
|
|
||||||
sigemptyset(&set);
|
|
||||||
sigaddset(&set, SIGINT);
|
|
||||||
sigaddset(&set, SIGTERM);
|
|
||||||
|
|
||||||
pthread_sigmask(SIG_BLOCK, &set, nullptr);
|
|
||||||
|
|
||||||
signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
|
|
||||||
if (signalFd < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance());
|
|
||||||
|
|
||||||
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
|
|
||||||
signalfd_siginfo fdsi;
|
|
||||||
::read(signalFd, &fdsi, sizeof(fdsi));
|
|
||||||
|
|
||||||
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
|
|
||||||
QCoreApplication::quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#elif defined(Q_OS_MACX)
|
|
||||||
static int signalPipe[2] = { -1, -1 };
|
|
||||||
static QSocketNotifier *socketNotifier = nullptr;
|
|
||||||
|
|
||||||
static void macSignalHandler(int)
|
|
||||||
{
|
|
||||||
const char ch = 1;
|
|
||||||
::write(signalPipe[1], &ch, sizeof(ch));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setupUnixSignalHandler()
|
|
||||||
{
|
|
||||||
if (::pipe(signalPipe) != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
|
|
||||||
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
|
|
||||||
|
|
||||||
struct sigaction sa {};
|
|
||||||
sa.sa_handler = macSignalHandler;
|
|
||||||
sigemptyset(&sa.sa_mask);
|
|
||||||
sa.sa_flags = 0;
|
|
||||||
|
|
||||||
sigaction(SIGINT, &sa, nullptr);
|
|
||||||
sigaction(SIGTERM, &sa, nullptr);
|
|
||||||
|
|
||||||
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
|
|
||||||
|
|
||||||
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
|
|
||||||
char buf[16];
|
|
||||||
::read(signalPipe[0], buf, sizeof(buf));
|
|
||||||
QCoreApplication::quit();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void cleanupUnixSignalHandler()
|
|
||||||
{
|
|
||||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
|
||||||
if (socketNotifier) {
|
|
||||||
socketNotifier->setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signalFd >= 0) {
|
|
||||||
::close(signalFd);
|
|
||||||
signalFd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(Q_OS_MACOS)
|
|
||||||
if (socketNotifier) {
|
|
||||||
socketNotifier->setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signalPipe[0] >= 0) {
|
|
||||||
::close(signalPipe[0]);
|
|
||||||
signalPipe[0] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signalPipe[1] >= 0) {
|
|
||||||
::close(signalPipe[1]);
|
|
||||||
signalPipe[1] = -1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void OsSignalHandler::setup()
|
|
||||||
{
|
|
||||||
if (initialized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
|
|
||||||
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACX)
|
|
||||||
setupUnixSignalHandler();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
SetConsoleCtrlHandler(consoleHandler, TRUE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#ifndef OSSIGNALHANDLER_H
|
|
||||||
#define OSSIGNALHANDLER_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
class OsSignalHandler : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
static void setup();
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit OsSignalHandler(QObject *parent = nullptr);
|
|
||||||
static void handleSignal(int signal);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // OSSIGNALHANDLER_H
|
|
||||||
@@ -21,7 +21,6 @@ namespace amnezia::serialization
|
|||||||
namespace vless
|
namespace vless
|
||||||
{
|
{
|
||||||
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||||
const QString Serialize(const VlessServerObject &server, const QString &alias);
|
|
||||||
} // namespace vless
|
} // namespace vless
|
||||||
|
|
||||||
namespace ss
|
namespace ss
|
||||||
|
|||||||
@@ -42,25 +42,6 @@ struct VMessServerObject
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct VlessServerObject
|
|
||||||
{
|
|
||||||
QString address;
|
|
||||||
QString id; // UUID
|
|
||||||
int port;
|
|
||||||
QString flow = "xtls-rprx-vision";
|
|
||||||
QString encryption = "none";
|
|
||||||
QString network = "tcp";
|
|
||||||
QString security = "reality";
|
|
||||||
QString serverName; // SNI
|
|
||||||
QString publicKey;
|
|
||||||
QString shortId;
|
|
||||||
QString fingerprint = "chrome";
|
|
||||||
QString spiderX = "";
|
|
||||||
JSONSTRUCT_COMPARE(VlessServerObject, address, id, port, flow, encryption)
|
|
||||||
JSONSTRUCT_REGISTER(VlessServerObject, F(address, id, port, flow, encryption, network, security, serverName, publicKey, shortId, fingerprint, spiderX))
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
namespace transfer
|
namespace transfer
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -252,65 +252,5 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
|||||||
root["inbounds"] = QJsonArray { inbound };
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
} // namespace amnezia::serialization::vless
|
||||||
const QString Serialize(const VlessServerObject &server, const QString &alias)
|
|
||||||
{
|
|
||||||
|
|
||||||
QUrl url;
|
|
||||||
|
|
||||||
// Set basic URL components
|
|
||||||
url.setScheme("vless");
|
|
||||||
url.setUserInfo(server.id);
|
|
||||||
url.setHost(server.address);
|
|
||||||
url.setPort(server.port);
|
|
||||||
|
|
||||||
QUrlQuery query;
|
|
||||||
|
|
||||||
if (!server.network.isEmpty() && server.network != "tcp") {
|
|
||||||
query.addQueryItem("type", server.network);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.encryption.isEmpty()) {
|
|
||||||
query.addQueryItem("encryption", server.encryption);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.security.isEmpty() && server.security != "none") {
|
|
||||||
query.addQueryItem("security", server.security);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.flow.isEmpty() && (server.security == "xtls" || server.security == "reality")) {
|
|
||||||
query.addQueryItem("flow", server.flow);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.serverName.isEmpty()) {
|
|
||||||
query.addQueryItem("sni", server.serverName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server.security == "reality") {
|
|
||||||
if (!server.fingerprint.isEmpty()) {
|
|
||||||
query.addQueryItem("fp", server.fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.publicKey.isEmpty()) {
|
|
||||||
query.addQueryItem("pbk", server.publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.shortId.isEmpty()) {
|
|
||||||
query.addQueryItem("sid", server.shortId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!server.spiderX.isEmpty()) {
|
|
||||||
query.addQueryItem("spiderX", server.spiderX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url.setQuery(query);
|
|
||||||
|
|
||||||
if (!alias.isEmpty()) {
|
|
||||||
url.setFragment(alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
const uint32_t S_IRWXU = 0644;
|
const uint32_t S_IRWXU = 0644;
|
||||||
@@ -290,6 +291,54 @@ namespace libssh {
|
|||||||
return watcher.result();
|
return watcher.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorCode Client::scpWriteBuffer(const ScpOverwriteMode overwriteMode, const QByteArray &data, const QString &remotePath, const QString &fileDesc)
|
||||||
|
{
|
||||||
|
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
|
||||||
|
|
||||||
|
if (m_scpSession == nullptr) {
|
||||||
|
return fromLibsshErrorCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssh_scp_init(m_scpSession) != SSH_OK) {
|
||||||
|
auto errorCode = fromLibsshErrorCode();
|
||||||
|
closeScpSession();
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFutureWatcher<ErrorCode> watcher;
|
||||||
|
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpWriteBufferFinished);
|
||||||
|
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &data, &remotePath, &fileDesc]() {
|
||||||
|
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
|
||||||
|
const int totalSize = data.size();
|
||||||
|
|
||||||
|
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), totalSize, accessType);
|
||||||
|
if (result != SSH_OK) {
|
||||||
|
return fromLibsshErrorCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int bufferSize = 16384;
|
||||||
|
int transferred = 0;
|
||||||
|
while (transferred < totalSize) {
|
||||||
|
const int chunkSize = std::min(bufferSize, totalSize - transferred);
|
||||||
|
result = ssh_scp_write(m_scpSession, data.constData() + transferred, chunkSize);
|
||||||
|
if (result != SSH_OK) {
|
||||||
|
return fromLibsshErrorCode();
|
||||||
|
}
|
||||||
|
transferred += chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
});
|
||||||
|
watcher.setFuture(future);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(this, &Client::scpWriteBufferFinished, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
closeScpSession();
|
||||||
|
return watcher.result();
|
||||||
|
}
|
||||||
|
|
||||||
void Client::closeScpSession()
|
void Client::closeScpSession()
|
||||||
{
|
{
|
||||||
if (m_scpSession != nullptr) {
|
if (m_scpSession != nullptr) {
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ namespace libssh {
|
|||||||
const QString &localPath,
|
const QString &localPath,
|
||||||
const QString &remotePath,
|
const QString &remotePath,
|
||||||
const QString &fileDesc);
|
const QString &fileDesc);
|
||||||
|
// Copy data directly without a temporary local file
|
||||||
|
ErrorCode scpWriteBuffer(const ScpOverwriteMode overwriteMode,
|
||||||
|
const QByteArray &data,
|
||||||
|
const QString &remotePath,
|
||||||
|
const QString &fileDesc);
|
||||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
||||||
private:
|
private:
|
||||||
ErrorCode closeChannel();
|
ErrorCode closeChannel();
|
||||||
@@ -52,6 +57,7 @@ namespace libssh {
|
|||||||
signals:
|
signals:
|
||||||
void writeToChannelFinished();
|
void writeToChannelFinished();
|
||||||
void scpFileCopyFinished();
|
void scpFileCopyFinished();
|
||||||
|
void scpWriteBufferFinished();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_4_34)">
|
|
||||||
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_4_34">
|
|
||||||
<rect width="74" height="74" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 982 B |
@@ -1,8 +0,0 @@
|
|||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<!-- Основа газеты -->
|
|
||||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
|
||||||
<!-- Линии текста -->
|
|
||||||
<line x1="7" y1="8" x2="17" y2="8"/>
|
|
||||||
<line x1="7" y1="12" x2="17" y2="12"/>
|
|
||||||
<line x1="7" y1="16" x2="13" y2="16"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 410 B |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 188 B |
@@ -2,7 +2,6 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "core/osSignalHandler.h"
|
|
||||||
#include "migrations.h"
|
#include "migrations.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
@@ -45,7 +44,6 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
AmneziaApplication app(argc, argv);
|
AmneziaApplication app(argc, argv);
|
||||||
OsSignalHandler::setup();
|
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
if (isAnotherInstanceRunning()) {
|
if (isAnotherInstanceRunning()) {
|
||||||
|
|||||||
@@ -99,9 +99,7 @@ bool AndroidController::initialize()
|
|||||||
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
||||||
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
||||||
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
||||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
|
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
||||||
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
|
|
||||||
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QJniEnvironment env;
|
QJniEnvironment env;
|
||||||
@@ -204,21 +202,6 @@ bool AndroidController::isOnTv()
|
|||||||
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidController::isEdgeToEdgeEnabled()
|
|
||||||
{
|
|
||||||
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidController::getStatusBarHeight()
|
|
||||||
{
|
|
||||||
return callActivityMethod<jint>("getStatusBarHeight", "()I");
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidController::getNavigationBarHeight()
|
|
||||||
{
|
|
||||||
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidController::startQrReaderActivity()
|
void AndroidController::startQrReaderActivity()
|
||||||
{
|
{
|
||||||
callActivityMethod("startQrCodeReader", "()V");
|
callActivityMethod("startQrCodeReader", "()V");
|
||||||
@@ -538,23 +521,3 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
|
|||||||
|
|
||||||
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
|
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
|
||||||
}
|
}
|
||||||
// static
|
|
||||||
void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
|
|
||||||
emit AndroidController::instance()->imeInsetsChanged(heightDp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
|
|
||||||
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ public:
|
|||||||
QString getFileName(const QString &uri);
|
QString getFileName(const QString &uri);
|
||||||
bool isCameraPresent();
|
bool isCameraPresent();
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
bool isEdgeToEdgeEnabled();
|
|
||||||
int getStatusBarHeight();
|
|
||||||
int getNavigationBarHeight();
|
|
||||||
void startQrReaderActivity();
|
void startQrReaderActivity();
|
||||||
void setSaveLogs(bool enabled);
|
void setSaveLogs(bool enabled);
|
||||||
void exportLogsFile(const QString &fileName);
|
void exportLogsFile(const QString &fileName);
|
||||||
@@ -73,8 +70,6 @@ signals:
|
|||||||
void importConfigFromOutside(QString config);
|
void importConfigFromOutside(QString config);
|
||||||
void initConnectionState(Vpn::ConnectionState state);
|
void initConnectionState(Vpn::ConnectionState state);
|
||||||
void authenticationResult(bool result);
|
void authenticationResult(bool result);
|
||||||
void imeInsetsChanged(int heightDp);
|
|
||||||
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isWaitingStatus = true;
|
bool isWaitingStatus = true;
|
||||||
@@ -103,8 +98,6 @@ private:
|
|||||||
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
||||||
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
|
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
|
||||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||||
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
|
|
||||||
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
|
|
||||||
|
|
||||||
template <typename Ret, typename ...Args>
|
template <typename Ret, typename ...Args>
|
||||||
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import Foundation
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
struct Log {
|
struct Log {
|
||||||
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
static let osLog = Logger()
|
||||||
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
|
|
||||||
|
|
||||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||||
static var isLoggingEnabled: Bool {
|
static var isLoggingEnabled: Bool {
|
||||||
@@ -78,41 +77,10 @@ struct Log {
|
|||||||
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||||
NSLog("\(title) \(message)")
|
NSLog("\(title) \(message)")
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .debug:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.debug("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.debug("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .info:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.info("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.info("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .error:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.error("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.error("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .fault:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.fault("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.fault("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.log("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.log("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard isLoggingEnabled else { return }
|
guard isLoggingEnabled else { return }
|
||||||
|
|
||||||
|
osLog.log(level: type, "\(title) \(message)")
|
||||||
|
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let level = Record.Level(from: type)
|
let level = Record.Level(from: type)
|
||||||
let messages = message.split(whereSeparator: \.isNewline)
|
let messages = message.split(whereSeparator: \.isNewline)
|
||||||
|
|||||||
@@ -1,76 +1,22 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
private let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
|
||||||
private let wireGuardSystemLogger = Logger(subsystem: subsystemIdentifier, category: "WireGuard")
|
|
||||||
private let openVPNSystemLogger = Logger(subsystem: subsystemIdentifier, category: "OpenVPN")
|
|
||||||
private let xraySystemLogger = Logger(subsystem: subsystemIdentifier, category: "Xray")
|
|
||||||
private let networkExtensionLogger = Logger(subsystem: subsystemIdentifier, category: "NetworkExtension")
|
|
||||||
|
|
||||||
private func logToSystem(_ logger: Logger, type: OSLogType, prefix: String, title: String, message: String) {
|
|
||||||
let combinedTitle: String
|
|
||||||
if title.isEmpty {
|
|
||||||
combinedTitle = prefix
|
|
||||||
} else {
|
|
||||||
combinedTitle = "\(prefix): \(title)"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .debug:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.debug("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.debug("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .info:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.info("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.info("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .error:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.error("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.error("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .fault:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.fault("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.fault("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.log("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.log("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||||
let stringMessage = String(describing: staticMessage)
|
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||||
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
|
|
||||||
neLog(type, title: "WG: \(title)", message: stringMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: message)
|
|
||||||
neLog(type, title: "WG: \(title)", message: message)
|
neLog(type, title: "WG: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(openVPNSystemLogger, type: type, prefix: "OVPN", title: title, message: message)
|
|
||||||
neLog(type, title: "OVPN: \(title)", message: message)
|
neLog(type, title: "OVPN: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(xraySystemLogger, type: type, prefix: "XRAY", title: title, message: message)
|
|
||||||
neLog(type, title: "XRAY: \(title)", message: message)
|
neLog(type, title: "XRAY: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(networkExtensionLogger, type: type, prefix: "NE", title: title, message: message)
|
|
||||||
Log.log(type, title: "NE: \(title)", message: message)
|
Log.log(type, title: "NE: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
struct OpenVPNConfig: Decodable {
|
struct OpenVPNConfig: Decodable {
|
||||||
let config: String
|
let config: String
|
||||||
@@ -28,83 +27,26 @@ extension PacketTunnelProvider {
|
|||||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
} catch {
|
} catch {
|
||||||
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||||
|
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func logOpenVPNError(_ error: NSError) {
|
|
||||||
let fatalFlag = (error.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false
|
|
||||||
var lines: [String] = []
|
|
||||||
lines.append("domain=\(error.domain) code=\(error.code) fatal=\(fatalFlag)")
|
|
||||||
|
|
||||||
if let adapterMessage = error.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !adapterMessage.isEmpty {
|
|
||||||
lines.append("message=\(adapterMessage)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let userInfoKeys = error.userInfo.keys.map { String(describing: $0) }.sorted()
|
|
||||||
if !userInfoKeys.isEmpty {
|
|
||||||
lines.append("userInfoKeys=[\(userInfoKeys.joined(separator: ","))]")
|
|
||||||
}
|
|
||||||
|
|
||||||
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? NSError {
|
|
||||||
lines.append("underlying=\(underlying.domain)#\(underlying.code) fatal=\((underlying.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false)")
|
|
||||||
if let underlyingMessage = underlying.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !underlyingMessage.isEmpty {
|
|
||||||
lines.append("underlyingMessage=\(underlyingMessage)")
|
|
||||||
} else if !underlying.localizedDescription.isEmpty {
|
|
||||||
lines.append("underlyingLocalized=\(underlying.localizedDescription)")
|
|
||||||
}
|
|
||||||
} else if let underlying = error.userInfo[NSUnderlyingErrorKey] {
|
|
||||||
lines.append("underlyingRaw=\(underlying)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatted = lines.joined(separator: "\n ")
|
|
||||||
ovpnLog(.error, title: "Error", message: formatted)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||||
withShadowSocks viaSS: Bool = false,
|
withShadowSocks viaSS: Bool = false,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
ovpnLog(.info, message: "Setup and launch")
|
ovpnLog(.info, message: "Setup and launch")
|
||||||
|
|
||||||
var configString = String(decoding: ovpnConfiguration, as: UTF8.self)
|
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
|
|
||||||
let digest = SHA256.hash(data: ovpnConfiguration)
|
|
||||||
let digestString = digest.map { String(format: "%02x", $0) }.joined()
|
|
||||||
ovpnLog(.info, title: "ConfigDigest", message: digestString)
|
|
||||||
|
|
||||||
let hasTlsAuthOpen = configString.contains("<tls-auth>")
|
|
||||||
let hasTlsAuthClose = configString.contains("</tls-auth>")
|
|
||||||
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
|
|
||||||
|
|
||||||
let lines = configString.split(separator: "\n")
|
|
||||||
let head = lines.prefix(10).joined(separator: "\n")
|
|
||||||
let tail = lines.suffix(10).joined(separator: "\n")
|
|
||||||
ovpnLog(.debug, title: "ConfigHead", message: head)
|
|
||||||
ovpnLog(.debug, title: "ConfigTail", message: tail)
|
|
||||||
|
|
||||||
if let start = configString.range(of: "<tls-auth>"),
|
|
||||||
let end = configString.range(of: "</tls-auth>", range: start.upperBound..<configString.endIndex) {
|
|
||||||
let keyBody = String(configString[start.upperBound..<end.lowerBound])
|
|
||||||
ovpnLog(.debug, title: "TLSAuthInline", message: keyBody)
|
|
||||||
let sanitizedLines = keyBody
|
|
||||||
.split(whereSeparator: { $0.isNewline })
|
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
||||||
.filter { !$0.isEmpty }
|
|
||||||
.filter { !$0.hasPrefix("#") }
|
|
||||||
|
|
||||||
let sanitizedKey = sanitizedLines.joined(separator: "\n")
|
|
||||||
ovpnLog(.debug, title: "TLSAuthSanitized", message: sanitizedKey)
|
|
||||||
let sanitizedBlock = "<tls-auth>\n\(sanitizedKey)\n</tls-auth>"
|
|
||||||
configString.replaceSubrange(start.lowerBound..<end.upperBound, with: sanitizedBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
let normalizedConfig = configString.replacingOccurrences(of: "\r\n", with: "\n")
|
|
||||||
let sanitizedData = Data(normalizedConfig.utf8)
|
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
let configuration = OpenVPNConfiguration()
|
||||||
configuration.fileContent = sanitizedData
|
configuration.fileContent = ovpnConfiguration
|
||||||
if configString.contains("cloak") {
|
if str.contains("cloak") {
|
||||||
configuration.setPTCloak()
|
configuration.setPTCloak()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +57,6 @@ extension PacketTunnelProvider {
|
|||||||
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
let nsError = error as NSError
|
|
||||||
ovpnLog(.error, title: "ApplyConfig", message: "domain=\(nsError.domain) code=\(nsError.code) info=\(nsError.userInfo)")
|
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -268,11 +208,8 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
|
|
||||||
// Handle errors thrown by the OpenVPN library
|
// Handle errors thrown by the OpenVPN library
|
||||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
||||||
let nsError = error as NSError
|
|
||||||
logOpenVPNError(nsError)
|
|
||||||
|
|
||||||
// Handle only fatal errors
|
// Handle only fatal errors
|
||||||
guard let fatal = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
||||||
fatal == true else { return }
|
fatal == true else { return }
|
||||||
|
|
||||||
if vpnReachability.isTracking {
|
if vpnReachability.isTracking {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
import WireGuardKitGo
|
||||||
|
|
||||||
enum XrayErrors: Error {
|
enum XrayErrors: Error {
|
||||||
case noXrayConfig
|
case noXrayConfig
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "../../mozilla/pingsender.h"
|
#include "../client/mozilla/pingsender.h"
|
||||||
|
|
||||||
class QSocketNotifier;
|
class QSocketNotifier;
|
||||||
|
|
||||||
|
|||||||
@@ -69,8 +69,6 @@ void* powerMonitoringThread(void* arg) {
|
|||||||
|
|
||||||
if (m_watcher) {
|
if (m_watcher) {
|
||||||
m_watcher->checkInterface();
|
m_watcher->checkInterface();
|
||||||
// Emit networkChanged signal when BSSID changes
|
|
||||||
emit m_watcher->networkChanged(QString::fromNSString(interfaceName));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ void OpenVpnProtocol::stop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||||
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
|
IpcClient::Interface()->disableKillSwitch();
|
||||||
disableKillSwitchResp.waitForFinished(1000);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ signals:
|
|||||||
void connectionStateChanged(Vpn::ConnectionState state);
|
void connectionStateChanged(Vpn::ConnectionState state);
|
||||||
void timeoutTimerEvent();
|
void timeoutTimerEvent();
|
||||||
void protocolError(amnezia::ErrorCode e);
|
void protocolError(amnezia::ErrorCode e);
|
||||||
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void onTimeout(); // todo: remove?
|
virtual void onTimeout(); // todo: remove?
|
||||||
|
|||||||
@@ -21,20 +21,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
|||||||
[this](const QString& serverIpv4Gateway,
|
[this](const QString& serverIpv4Gateway,
|
||||||
const QString& deviceIpv4Address, uint64_t txBytes,
|
const QString& deviceIpv4Address, uint64_t txBytes,
|
||||||
uint64_t rxBytes) {
|
uint64_t rxBytes) {
|
||||||
const QString previousGateway = m_vpnGateway;
|
m_vpnLocalAddress = deviceIpv4Address;
|
||||||
const QString previousLocal = m_vpnLocalAddress;
|
|
||||||
|
|
||||||
if (!serverIpv4Gateway.isEmpty()) {
|
|
||||||
m_vpnGateway = serverIpv4Gateway;
|
|
||||||
}
|
|
||||||
if (!deviceIpv4Address.isEmpty()) {
|
|
||||||
m_vpnLocalAddress = deviceIpv4Address;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
|
|
||||||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
|
|
||||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||||
@@ -77,3 +64,4 @@ ErrorCode WireguardProtocol::start()
|
|||||||
{
|
{
|
||||||
return startMzImpl();
|
return startMzImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,17 +166,9 @@ ErrorCode XrayProtocol::startTun2Sock()
|
|||||||
|
|
||||||
void XrayProtocol::stop()
|
void XrayProtocol::stop()
|
||||||
{
|
{
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||||
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
|
IpcClient::Interface()->disableKillSwitch();
|
||||||
disableKillSwitchResp.waitForFinished(1000);
|
IpcClient::Interface()->StartRoutingIpv6();
|
||||||
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = IpcClient::Interface()->StartRoutingIpv6();
|
|
||||||
StartRoutingIpv6Resp.waitForFinished(1000);
|
|
||||||
QRemoteObjectPendingReply<bool> restoreResolvers = IpcClient::Interface()->restoreResolvers();
|
|
||||||
restoreResolvers.waitForFinished(1000);
|
|
||||||
#if !defined(Q_OS_MACOS)
|
|
||||||
QRemoteObjectPendingReply<bool> deleteTunResp = IpcClient::Interface()->deleteTun("tun2");
|
|
||||||
deleteTunResp.waitForFinished(1000);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
qDebug() << "XrayProtocol::stop()";
|
qDebug() << "XrayProtocol::stop()";
|
||||||
m_xrayProcess.disconnect();
|
m_xrayProcess.disconnect();
|
||||||
@@ -184,7 +176,6 @@ void XrayProtocol::stop()
|
|||||||
m_xrayProcess.waitForFinished(3000);
|
m_xrayProcess.waitForFinished(3000);
|
||||||
if (m_t2sProcess) {
|
if (m_t2sProcess) {
|
||||||
m_t2sProcess->stop();
|
m_t2sProcess->stop();
|
||||||
QThread::msleep(200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
|
|||||||
@@ -35,9 +35,6 @@
|
|||||||
<file>images/controls/mail.svg</file>
|
<file>images/controls/mail.svg</file>
|
||||||
<file>images/controls/map-pin.svg</file>
|
<file>images/controls/map-pin.svg</file>
|
||||||
<file>images/controls/more-vertical.svg</file>
|
<file>images/controls/more-vertical.svg</file>
|
||||||
<file>images/controls/news.svg</file>
|
|
||||||
<file>images/controls/news-unread.svg</file>
|
|
||||||
<file>images/controls/unread-dot.svg</file>
|
|
||||||
<file>images/controls/plus.svg</file>
|
<file>images/controls/plus.svg</file>
|
||||||
<file>images/controls/qr-code.svg</file>
|
<file>images/controls/qr-code.svg</file>
|
||||||
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
||||||
@@ -52,7 +49,6 @@
|
|||||||
<file>images/controls/server.svg</file>
|
<file>images/controls/server.svg</file>
|
||||||
<file>images/controls/settings-2.svg</file>
|
<file>images/controls/settings-2.svg</file>
|
||||||
<file>images/controls/settings.svg</file>
|
<file>images/controls/settings.svg</file>
|
||||||
<file>images/controls/settings-news.svg</file>
|
|
||||||
<file>images/controls/share-2.svg</file>
|
<file>images/controls/share-2.svg</file>
|
||||||
<file>images/controls/split-tunneling.svg</file>
|
<file>images/controls/split-tunneling.svg</file>
|
||||||
<file>images/controls/tag.svg</file>
|
<file>images/controls/tag.svg</file>
|
||||||
@@ -131,6 +127,7 @@
|
|||||||
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
||||||
<file>ui/qml/Components/ServersListView.qml</file>
|
<file>ui/qml/Components/ServersListView.qml</file>
|
||||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||||
|
|
||||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||||
<file>ui/qml/Components/AddSitePanel.qml</file>
|
<file>ui/qml/Components/AddSitePanel.qml</file>
|
||||||
<file>ui/qml/Config/GlobalConfig.qml</file>
|
<file>ui/qml/Config/GlobalConfig.qml</file>
|
||||||
@@ -215,8 +212,6 @@
|
|||||||
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsNewsNotifications.qml</file>
|
|
||||||
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
|
|
||||||
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||||
@@ -247,7 +242,6 @@
|
|||||||
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
|
||||||
<file>ui/qml/Components/AwgTextField.qml</file>
|
<file>ui/qml/Components/AwgTextField.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
|
||||||
<file>ui/qml/Components/SmartScroll.qml</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/countriesFlags">
|
<qresource prefix="/countriesFlags">
|
||||||
<file>images/flagKit/ZW.svg</file>
|
<file>images/flagKit/ZW.svg</file>
|
||||||
|
|||||||
+2
-12
@@ -541,12 +541,12 @@ QString Settings::getGatewayEndpoint()
|
|||||||
|
|
||||||
bool Settings::isDevGatewayEnv()
|
bool Settings::isDevGatewayEnv()
|
||||||
{
|
{
|
||||||
return value("Conf/devGatewayEnv", false).toBool();
|
return m_isDevGatewayEnv;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::toggleDevGatewayEnv(bool enabled)
|
void Settings::toggleDevGatewayEnv(bool enabled)
|
||||||
{
|
{
|
||||||
setValue("Conf/devGatewayEnv", enabled);
|
m_isDevGatewayEnv = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isHomeAdLabelVisible()
|
bool Settings::isHomeAdLabelVisible()
|
||||||
@@ -578,13 +578,3 @@ void Settings::setAllowedDnsServers(const QStringList &servers)
|
|||||||
{
|
{
|
||||||
setValue("Conf/allowedDnsServers", servers);
|
setValue("Conf/allowedDnsServers", servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Settings::readNewsIds() const
|
|
||||||
{
|
|
||||||
return value("News/readIds").toStringList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::setReadNewsIds(const QStringList &ids)
|
|
||||||
{
|
|
||||||
setValue("News/readIds", ids);
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-3
@@ -236,9 +236,6 @@ public:
|
|||||||
QStringList allowedDnsServers() const;
|
QStringList allowedDnsServers() const;
|
||||||
void setAllowedDnsServers(const QStringList &servers);
|
void setAllowedDnsServers(const QStringList &servers);
|
||||||
|
|
||||||
QStringList readNewsIds() const;
|
|
||||||
void setReadNewsIds(const QStringList &ids);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void saveLogsChanged(bool enabled);
|
void saveLogsChanged(bool enabled);
|
||||||
void screenshotsEnabledChanged(bool enabled);
|
void screenshotsEnabledChanged(bool enabled);
|
||||||
@@ -254,6 +251,7 @@ private:
|
|||||||
mutable SecureQSettings m_settings;
|
mutable SecureQSettings m_settings;
|
||||||
|
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
|
bool m_isDevGatewayEnv = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGS_H
|
#endif // SETTINGS_H
|
||||||
|
|||||||
+1413
-802
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1413
-810
File diff suppressed because it is too large
Load Diff
+1413
-802
File diff suppressed because it is too large
Load Diff
+1416
-801
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,9 @@
|
|||||||
<context>
|
<context>
|
||||||
<name>AdLabel</name>
|
<name>AdLabel</name>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../ui/qml/Components/AdLabel.qml" line="57"/>
|
||||||
<source>Amnezia Premium - for access to all websites and online resources</source>
|
<source>Amnezia Premium - for access to all websites and online resources</source>
|
||||||
<translation type="vanished">Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам</translation>
|
<translation>Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
<name>ApiAccountInfoModel</name>
|
<name>ApiAccountInfoModel</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="31"/>
|
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="31"/>
|
||||||
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="35"/>
|
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="34"/>
|
||||||
<source>Active</source>
|
<source>Active</source>
|
||||||
<translation>Активна</translation>
|
<translation>Активна</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -70,33 +71,35 @@
|
|||||||
<translation>Не активна</translation>
|
<translation>Не активна</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="48"/>
|
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="47"/>
|
||||||
<source>%1 out of %2</source>
|
<source>%1 out of %2</source>
|
||||||
<translation>%1 из %2</translation>
|
<translation>%1 из %2</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="51"/>
|
||||||
<source>Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to 200 Mbps</source>
|
<source>Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to 200 Mbps</source>
|
||||||
<translation type="vanished">Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость — до 200 Мбит/с</translation>
|
<translation>Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость — до 200 Мбит/с</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../ui/models/api/apiAccountInfoModel.cpp" line="55"/>
|
||||||
<source>Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and more. YouTube is not included in the free plan.</source>
|
<source>Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and more. YouTube is not included in the free plan.</source>
|
||||||
<translation type="vanished">Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф.</translation>
|
<translation>Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф.</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ApiConfigsController</name>
|
<name>ApiConfigsController</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="448"/>
|
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="413"/>
|
||||||
<source>%1 installed successfully.</source>
|
<source>%1 installed successfully.</source>
|
||||||
<translation>%1 успешно установлен.</translation>
|
<translation>%1 успешно установлен.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="513"/>
|
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="473"/>
|
||||||
<source>API config reloaded</source>
|
<source>API config reloaded</source>
|
||||||
<translation>Конфигурация API перезагружена</translation>
|
<translation>Конфигурация API перезагружена</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="517"/>
|
<location filename="../ui/controllers/api/apiConfigsController.cpp" line="477"/>
|
||||||
<source>Successfully changed the country of connection to %1</source>
|
<source>Successfully changed the country of connection to %1</source>
|
||||||
<translation>Страна подключения изменена на %1</translation>
|
<translation>Страна подключения изменена на %1</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -624,32 +627,27 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Продолжить</translation>
|
<translation>Продолжить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="92"/>
|
<location filename="../ui/qml/Pages2/PageHome.qml" line="102"/>
|
||||||
<source>Logging enabled</source>
|
<source>Logging enabled</source>
|
||||||
<translation>Логирование включено</translation>
|
<translation>Логирование включено</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="120"/>
|
<location filename="../ui/qml/Pages2/PageHome.qml" line="144"/>
|
||||||
<source>Dev gateway enabled</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="162"/>
|
|
||||||
<source>Split tunneling enabled</source>
|
<source>Split tunneling enabled</source>
|
||||||
<translation>Раздельное туннелирование включено</translation>
|
<translation>Раздельное туннелирование включено</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="162"/>
|
<location filename="../ui/qml/Pages2/PageHome.qml" line="144"/>
|
||||||
<source>Split tunneling disabled</source>
|
<source>Split tunneling disabled</source>
|
||||||
<translation>Раздельное туннелирование выключено</translation>
|
<translation>Раздельное туннелирование выключено</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="409"/>
|
<location filename="../ui/qml/Pages2/PageHome.qml" line="381"/>
|
||||||
<source>VPN protocol</source>
|
<source>VPN protocol</source>
|
||||||
<translation>VPN-протокол</translation>
|
<translation>VPN-протокол</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageHome.qml" line="462"/>
|
<location filename="../ui/qml/Pages2/PageHome.qml" line="434"/>
|
||||||
<source>Servers</source>
|
<source>Servers</source>
|
||||||
<translation>Серверы</translation>
|
<translation>Серверы</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -1581,37 +1579,32 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Настройки</translation>
|
<translation>Настройки</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="102"/>
|
<location filename="../ui/qml/Pages2/PageSettings.qml" line="101"/>
|
||||||
<source>Servers</source>
|
<source>Servers</source>
|
||||||
<translation>Серверы</translation>
|
<translation>Серверы</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="113"/>
|
<location filename="../ui/qml/Pages2/PageSettings.qml" line="112"/>
|
||||||
<source>Connection</source>
|
<source>Connection</source>
|
||||||
<translation>Соединение</translation>
|
<translation>Соединение</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="124"/>
|
<location filename="../ui/qml/Pages2/PageSettings.qml" line="123"/>
|
||||||
<source>Application</source>
|
<source>Application</source>
|
||||||
<translation>Приложение</translation>
|
<translation>Приложение</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="135"/>
|
<location filename="../ui/qml/Pages2/PageSettings.qml" line="134"/>
|
||||||
<source>News & Notifications</source>
|
|
||||||
<translation>Новости и Уведомления</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="152"/>
|
|
||||||
<source>Backup</source>
|
<source>Backup</source>
|
||||||
<translation>Резервное копирование</translation>
|
<translation>Резервное копирование</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="163"/>
|
<location filename="../ui/qml/Pages2/PageSettings.qml" line="145"/>
|
||||||
<source>About AmneziaVPN</source>
|
<source>About AmneziaVPN</source>
|
||||||
<translation>Об AmneziaVPN</translation>
|
<translation>Об AmneziaVPN</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettings.qml" line="174"/>
|
<location filename="../ui/qml/Pages2/PageSettings.qml" line="156"/>
|
||||||
<source>Dev console</source>
|
<source>Dev console</source>
|
||||||
<translation>Dev console</translation>
|
<translation>Dev console</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -2086,40 +2079,52 @@ Thank you for staying with us!</source>
|
|||||||
<context>
|
<context>
|
||||||
<name>PageSettingsApiSubscriptionKey</name>
|
<name>PageSettingsApiSubscriptionKey</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="85"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="43"/>
|
||||||
|
<source>Amnezia Premium
|
||||||
|
subscription key</source>
|
||||||
|
<translation>Amnezia Premium
|
||||||
|
ключ подключения</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="56"/>
|
||||||
<source>Copy key</source>
|
<source>Copy key</source>
|
||||||
<translation>Скопировать ключ</translation>
|
<translation>Скопировать ключ</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="90"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="61"/>
|
||||||
<source>Copied</source>
|
<source>Copied</source>
|
||||||
<translation>Скопировано</translation>
|
<translation>Скопировано</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="106"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="77"/>
|
||||||
<source>Save key as a file</source>
|
<source>Save key as a file</source>
|
||||||
<translation>Сохранить ключ как файл</translation>
|
<translation>Сохранить ключ как файл</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="113"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="84"/>
|
||||||
<source>Save AmneziaVPN config</source>
|
<source>Save AmneziaVPN config</source>
|
||||||
<translation>Сохранить конфигурацию AmneziaVPN</translation>
|
<translation>Сохранить конфигурацию AmneziaVPN</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="114"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="85"/>
|
||||||
<source>Config files (*.vpn)</source>
|
<source>Config files (*.vpn)</source>
|
||||||
<translation>Файлы конфигов (*.vpn)</translation>
|
<translation>Файлы конфигов (*.vpn)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="139"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="110"/>
|
||||||
<source>Show key text</source>
|
<source>Show key text</source>
|
||||||
<translation>Показать ключ</translation>
|
<translation>Показать ключ</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="180"/>
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="151"/>
|
||||||
<source>To read the QR code in the Amnezia app, tap + in the main menu → 'QR code'</source>
|
<source>To read the QR code in the Amnezia app, tap + in the main menu → 'QR code'</source>
|
||||||
<translation>Для считывания QR-кода в приложении Amnezia выберите + в главном меню → 'QR-код'</translation>
|
<translation>Для считывания QR-кода в приложении Amnezia выберите + в главном меню → 'QR-код'</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml" line="180"/>
|
||||||
|
<source>Amnezia Premium Subscription key</source>
|
||||||
|
<translation>Ключ подключения Amnezia Premium</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PageSettingsApiSupport</name>
|
<name>PageSettingsApiSupport</name>
|
||||||
@@ -2192,37 +2197,32 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Режим</translation>
|
<translation>Режим</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="155"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="185"/>
|
||||||
<source>Only "Apps from the list should not have access via VPN" mode is available on Windows</source>
|
|
||||||
<translation>На Windows доступен только режим "Приложения из списка не должны работать через VPN"</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="199"/>
|
|
||||||
<source>Remove </source>
|
<source>Remove </source>
|
||||||
<translation>Удалить </translation>
|
<translation>Удалить </translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="200"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="186"/>
|
||||||
<source>Continue</source>
|
<source>Continue</source>
|
||||||
<translation>Продолжить</translation>
|
<translation>Продолжить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="201"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="187"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Отменить</translation>
|
<translation>Отменить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="242"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="228"/>
|
||||||
<source>application name</source>
|
<source>application name</source>
|
||||||
<translation>название приложения</translation>
|
<translation>название приложения</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="252"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="238"/>
|
||||||
<source>Open executable file</source>
|
<source>Open executable file</source>
|
||||||
<translation>Открыть исполняемый файл</translation>
|
<translation>Открыть исполняемый файл</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="253"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="239"/>
|
||||||
<source>Executable files (*.*)</source>
|
<source>Executable files (*.*)</source>
|
||||||
<translation>Исполняемые файлы (*.*)</translation>
|
<translation>Исполняемые файлы (*.*)</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -2770,14 +2770,6 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Очистить логи</translation>
|
<translation>Очистить логи</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
|
||||||
<name>PageSettingsNewsNotifications</name>
|
|
||||||
<message>
|
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsNewsNotifications.qml" line="33"/>
|
|
||||||
<source>News & Notifications</source>
|
|
||||||
<translation>Новости и Уведомления</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
<context>
|
||||||
<name>PageSettingsServerData</name>
|
<name>PageSettingsServerData</name>
|
||||||
<message>
|
<message>
|
||||||
@@ -3021,19 +3013,19 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Режим</translation>
|
<translation>Режим</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="209"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/>
|
||||||
<source>Remove </source>
|
<source>Remove </source>
|
||||||
<translation>Удалить </translation>
|
<translation>Удалить </translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="210"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="208"/>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="357"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="356"/>
|
||||||
<source>Continue</source>
|
<source>Continue</source>
|
||||||
<translation>Продолжить</translation>
|
<translation>Продолжить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="211"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="209"/>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="358"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="357"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Отменить</translation>
|
<translation>Отменить</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -3048,70 +3040,70 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Невозможно изменить настройки раздельного туннелирования во время активного соединения</translation>
|
<translation>Невозможно изменить настройки раздельного туннелирования во время активного соединения</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="259"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="257"/>
|
||||||
<source>website or IP</source>
|
<source>website or IP</source>
|
||||||
<translation>веб-сайт или IP</translation>
|
<translation>веб-сайт или IP</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="302"/>
|
||||||
<source>Additional options</source>
|
<source>Additional options</source>
|
||||||
<translation>Дополнительные настройки</translation>
|
<translation>Дополнительные настройки</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="311"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="309"/>
|
||||||
<source>Import</source>
|
<source>Import</source>
|
||||||
<translation>Импорт</translation>
|
<translation>Импорт</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="322"/>
|
||||||
<source>Save site list</source>
|
<source>Save site list</source>
|
||||||
<translation>Сохранить список сайтов</translation>
|
<translation>Сохранить список сайтов</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="331"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="329"/>
|
||||||
<source>Save sites</source>
|
<source>Save sites</source>
|
||||||
<translation>Сохранить сайты</translation>
|
<translation>Сохранить сайты</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="332"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="330"/>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="458"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="457"/>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="471"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="470"/>
|
||||||
<source>Sites files (*.json)</source>
|
<source>Sites files (*.json)</source>
|
||||||
<translation>Файлы сайтов (*.json)</translation>
|
<translation>Файлы сайтов (*.json)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="352"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="350"/>
|
||||||
<source>Clear site list</source>
|
<source>Clear site list</source>
|
||||||
<translation>Очистить список сайтов</translation>
|
<translation>Очистить список сайтов</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="355"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="354"/>
|
||||||
<source>Clear site list?</source>
|
<source>Clear site list?</source>
|
||||||
<translation>Очистить список сайтов?</translation>
|
<translation>Очистить список сайтов?</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="356"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="355"/>
|
||||||
<source>All sites will be removed from list.</source>
|
<source>All sites will be removed from list.</source>
|
||||||
<translation>Все сайты будут удалены из списка.</translation>
|
<translation>Все сайты будут удалены из списка.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="420"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="419"/>
|
||||||
<source>Import a list of sites</source>
|
<source>Import a list of sites</source>
|
||||||
<translation>Импортировать список с сайтами</translation>
|
<translation>Импортировать список с сайтами</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="455"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="454"/>
|
||||||
<source>Replace site list</source>
|
<source>Replace site list</source>
|
||||||
<translation>Заменить список с сайтами</translation>
|
<translation>Заменить список с сайтами</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="457"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="456"/>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="470"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="469"/>
|
||||||
<source>Open sites file</source>
|
<source>Open sites file</source>
|
||||||
<translation>Открыть список с сайтами</translation>
|
<translation>Открыть список с сайтами</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="468"/>
|
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="467"/>
|
||||||
<source>Add imported sites to existing ones</source>
|
<source>Add imported sites to existing ones</source>
|
||||||
<translation>Добавить импортированные сайты к существующим</translation>
|
<translation>Добавить импортированные сайты к существующим</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -3536,32 +3528,32 @@ Thank you for staying with us!</source>
|
|||||||
<context>
|
<context>
|
||||||
<name>PageSetupWizardViewConfig</name>
|
<name>PageSetupWizardViewConfig</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="72"/>
|
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="70"/>
|
||||||
<source>New connection</source>
|
<source>New connection</source>
|
||||||
<translation>Новое соединение</translation>
|
<translation>Новое соединение</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="112"/>
|
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||||
<source>Collapse content</source>
|
<source>Collapse content</source>
|
||||||
<translation>Свернуть</translation>
|
<translation>Свернуть</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="112"/>
|
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
|
||||||
<source>Show content</source>
|
<source>Show content</source>
|
||||||
<translation>Показать</translation>
|
<translation>Показать</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="129"/>
|
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="127"/>
|
||||||
<source>Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.</source>
|
<source>Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.</source>
|
||||||
<translation>Включить обфускацию WireGuard. Это может быть полезно, если WireGuard блокируется вашим провайдером.</translation>
|
<translation>Включить обфускацию WireGuard. Это может быть полезно, если WireGuard блокируется вашим провайдером.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="160"/>
|
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="158"/>
|
||||||
<source>Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.</source>
|
<source>Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.</source>
|
||||||
<translation>Используйте файлы конфигурации только из тех источников, которым вы доверяете. Файлы из общедоступных источников могли быть созданы с целью перехвата ваших личных данных.</translation>
|
<translation>Используйте файлы конфигурации только из тех источников, которым вы доверяете. Файлы из общедоступных источников могли быть созданы с целью перехвата ваших личных данных.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="204"/>
|
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="202"/>
|
||||||
<source>Connect</source>
|
<source>Connect</source>
|
||||||
<translation>Подключиться</translation>
|
<translation>Подключиться</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -3696,67 +3688,67 @@ Thank you for staying with us!</source>
|
|||||||
<translation>Поиск</translation>
|
<translation>Поиск</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="691"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="689"/>
|
||||||
<source>Creation date: %1</source>
|
<source>Creation date: %1</source>
|
||||||
<translation>Дата создания: %1</translation>
|
<translation>Дата создания: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="703"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/>
|
||||||
<source>Latest handshake: %1</source>
|
<source>Latest handshake: %1</source>
|
||||||
<translation>Последнее рукопожатие: %1</translation>
|
<translation>Последнее рукопожатие: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="715"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="713"/>
|
||||||
<source>Data received: %1</source>
|
<source>Data received: %1</source>
|
||||||
<translation>Получено данных: %1</translation>
|
<translation>Получено данных: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="727"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="725"/>
|
||||||
<source>Data sent: %1</source>
|
<source>Data sent: %1</source>
|
||||||
<translation>Отправлено данных: %1</translation>
|
<translation>Отправлено данных: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="737"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="735"/>
|
||||||
<source>Allowed IPs: %1</source>
|
<source>Allowed IPs: %1</source>
|
||||||
<translation>Разрешенные подсети: %1</translation>
|
<translation>Разрешенные подсети: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="752"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="750"/>
|
||||||
<source>Rename</source>
|
<source>Rename</source>
|
||||||
<translation>Переименовать</translation>
|
<translation>Переименовать</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="777"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="775"/>
|
||||||
<source>Client name</source>
|
<source>Client name</source>
|
||||||
<translation>Имя клиента</translation>
|
<translation>Имя клиента</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="788"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="786"/>
|
||||||
<source>Save</source>
|
<source>Save</source>
|
||||||
<translation>Сохранить</translation>
|
<translation>Сохранить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="824"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="820"/>
|
||||||
<source>Revoke</source>
|
<source>Revoke</source>
|
||||||
<translation>Отозвать</translation>
|
<translation>Отозвать</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="827"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="823"/>
|
||||||
<source>Revoke the config for a user - %1?</source>
|
<source>Revoke the config for a user - %1?</source>
|
||||||
<translation>Отозвать конфигурацию для пользователя - %1?</translation>
|
<translation>Отозвать конфигурацию для пользователя - %1?</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="828"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="824"/>
|
||||||
<source>The user will no longer be able to connect to your server.</source>
|
<source>The user will no longer be able to connect to your server.</source>
|
||||||
<translation>Пользователь больше не сможет подключаться к вашему серверу.</translation>
|
<translation>Пользователь больше не сможет подключаться к вашему серверу.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="829"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="825"/>
|
||||||
<source>Continue</source>
|
<source>Continue</source>
|
||||||
<translation>Продолжить</translation>
|
<translation>Продолжить</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageShare.qml" line="830"/>
|
<location filename="../ui/qml/Pages2/PageShare.qml" line="826"/>
|
||||||
<source>Cancel</source>
|
<source>Cancel</source>
|
||||||
<translation>Отменить</translation>
|
<translation>Отменить</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -4965,12 +4957,12 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
|||||||
<context>
|
<context>
|
||||||
<name>SettingsController</name>
|
<name>SettingsController</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/controllers/settingsController.cpp" line="258"/>
|
<location filename="../ui/controllers/settingsController.cpp" line="242"/>
|
||||||
<source>All settings have been reset to default values</source>
|
<source>All settings have been reset to default values</source>
|
||||||
<translation>Все настройки сброшены до значений по умолчанию</translation>
|
<translation>Все настройки сброшены до значений по умолчанию</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/controllers/settingsController.cpp" line="235"/>
|
<location filename="../ui/controllers/settingsController.cpp" line="219"/>
|
||||||
<source>Backup file is corrupted</source>
|
<source>Backup file is corrupted</source>
|
||||||
<translation>Файл резервной копии поврежден</translation>
|
<translation>Файл резервной копии поврежден</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -5059,7 +5051,7 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
|||||||
<context>
|
<context>
|
||||||
<name>TextFieldWithHeaderType</name>
|
<name>TextFieldWithHeaderType</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Controls2/TextFieldWithHeaderType.qml" line="133"/>
|
<location filename="../ui/qml/Controls2/TextFieldWithHeaderType.qml" line="117"/>
|
||||||
<source>The field can't be empty</source>
|
<source>The field can't be empty</source>
|
||||||
<translation>Поле не может быть пустым</translation>
|
<translation>Поле не может быть пустым</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -5067,7 +5059,7 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
|||||||
<context>
|
<context>
|
||||||
<name>VpnConnection</name>
|
<name>VpnConnection</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../vpnconnection.cpp" line="437"/>
|
<location filename="../vpnconnection.cpp" line="492"/>
|
||||||
<source>Mbps</source>
|
<source>Mbps</source>
|
||||||
<translation>Мбит/с</translation>
|
<translation>Мбит/с</translation>
|
||||||
</message>
|
</message>
|
||||||
@@ -5075,42 +5067,42 @@ FileZilla или другие SFTP-клиенты, а также смонтир
|
|||||||
<context>
|
<context>
|
||||||
<name>VpnProtocol</name>
|
<name>VpnProtocol</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="133"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="138"/>
|
||||||
<source>Unknown</source>
|
<source>Unknown</source>
|
||||||
<translation>Неизвестный</translation>
|
<translation>Неизвестный</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="134"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="139"/>
|
||||||
<source>Disconnected</source>
|
<source>Disconnected</source>
|
||||||
<translation>Отключено</translation>
|
<translation>Отключено</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="135"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="140"/>
|
||||||
<source>Preparing</source>
|
<source>Preparing</source>
|
||||||
<translation>Подготовка</translation>
|
<translation>Подготовка</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="136"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="141"/>
|
||||||
<source>Connecting...</source>
|
<source>Connecting...</source>
|
||||||
<translation>Подключение...</translation>
|
<translation>Подключение...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="137"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="142"/>
|
||||||
<source>Connected</source>
|
<source>Connected</source>
|
||||||
<translation>Подключено</translation>
|
<translation>Подключено</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="138"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="143"/>
|
||||||
<source>Disconnecting...</source>
|
<source>Disconnecting...</source>
|
||||||
<translation>Отключение...</translation>
|
<translation>Отключение...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="139"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="144"/>
|
||||||
<source>Reconnecting...</source>
|
<source>Reconnecting...</source>
|
||||||
<translation>Переподключение...</translation>
|
<translation>Переподключение...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../protocols/vpnprotocol.cpp" line="140"/>
|
<location filename="../protocols/vpnprotocol.cpp" line="145"/>
|
||||||
<source>Error</source>
|
<source>Error</source>
|
||||||
<translation>Ошибка</translation>
|
<translation>Ошибка</translation>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+1430
-823
File diff suppressed because it is too large
Load Diff
+1410
-803
File diff suppressed because it is too large
Load Diff
+1425
-830
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ namespace
|
|||||||
constexpr char uuid[] = "installation_uuid";
|
constexpr char uuid[] = "installation_uuid";
|
||||||
constexpr char osVersion[] = "os_version";
|
constexpr char osVersion[] = "os_version";
|
||||||
constexpr char appVersion[] = "app_version";
|
constexpr char appVersion[] = "app_version";
|
||||||
|
constexpr char appLanguage[] = "app_language";
|
||||||
|
|
||||||
constexpr char userCountryCode[] = "user_country_code";
|
constexpr char userCountryCode[] = "user_country_code";
|
||||||
constexpr char serverCountryCode[] = "server_country_code";
|
constexpr char serverCountryCode[] = "server_country_code";
|
||||||
@@ -46,8 +47,6 @@ namespace
|
|||||||
|
|
||||||
constexpr char subscription[] = "subscription";
|
constexpr char subscription[] = "subscription";
|
||||||
constexpr char endDate[] = "end_date";
|
constexpr char endDate[] = "end_date";
|
||||||
|
|
||||||
constexpr char isConnectEvent[] = "is_connect_event";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProtocolData
|
struct ProtocolData
|
||||||
@@ -64,7 +63,6 @@ namespace
|
|||||||
{
|
{
|
||||||
QString osVersion;
|
QString osVersion;
|
||||||
QString appVersion;
|
QString appVersion;
|
||||||
QString appLanguage;
|
|
||||||
|
|
||||||
QString installationUuid;
|
QString installationUuid;
|
||||||
|
|
||||||
@@ -84,9 +82,6 @@ namespace
|
|||||||
if (!appVersion.isEmpty()) {
|
if (!appVersion.isEmpty()) {
|
||||||
obj[configKey::appVersion] = appVersion;
|
obj[configKey::appVersion] = appVersion;
|
||||||
}
|
}
|
||||||
if (!appLanguage.isEmpty()) {
|
|
||||||
obj[apiDefs::key::appLanguage] = appLanguage;
|
|
||||||
}
|
|
||||||
if (!installationUuid.isEmpty()) {
|
if (!installationUuid.isEmpty()) {
|
||||||
obj[configKey::uuid] = installationUuid;
|
obj[configKey::uuid] = installationUuid;
|
||||||
}
|
}
|
||||||
@@ -226,9 +221,6 @@ namespace
|
|||||||
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
|
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
|
||||||
apiConfig.insert(apiDefs::key::supportedProtocols,
|
apiConfig.insert(apiDefs::key::supportedProtocols,
|
||||||
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
|
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
|
||||||
|
|
||||||
apiConfig.insert(apiDefs::key::serviceInfo,
|
|
||||||
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::serviceInfo).toObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serverConfig[configKey::apiConfig] = apiConfig;
|
serverConfig[configKey::apiConfig] = apiConfig;
|
||||||
@@ -257,23 +249,6 @@ ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &s
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::exportVpnKey(const QString &fileName)
|
|
||||||
{
|
|
||||||
if (fileName.isEmpty()) {
|
|
||||||
emit errorOccurred(ErrorCode::PermissionsError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareVpnKeyExport();
|
|
||||||
if (m_vpnKey.isEmpty()) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemController::saveFile(fileName, m_vpnKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
|
bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, const QString &fileName)
|
||||||
{
|
{
|
||||||
if (fileName.isEmpty()) {
|
if (fileName.isEmpty()) {
|
||||||
@@ -291,7 +266,6 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
|||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||||
serverCountryCode,
|
serverCountryCode,
|
||||||
@@ -332,7 +306,6 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
|
|||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||||
serverCountryCode,
|
serverCountryCode,
|
||||||
@@ -357,13 +330,6 @@ void ApiConfigsController::prepareVpnKeyExport()
|
|||||||
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
|
||||||
|
|
||||||
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
|
auto vpnKey = apiConfigObject.value(apiDefs::key::vpnKey).toString();
|
||||||
if (vpnKey.isEmpty()) {
|
|
||||||
vpnKey = apiUtils::getPremiumV2VpnKey(serverConfigObject);
|
|
||||||
apiConfigObject.insert(apiDefs::key::vpnKey, vpnKey);
|
|
||||||
serverConfigObject.insert(configKey::apiConfig, apiConfigObject);
|
|
||||||
m_serversModel->editServer(serverConfigObject, m_serversModel->getProcessedServerIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_vpnKey = vpnKey;
|
m_vpnKey = vpnKey;
|
||||||
|
|
||||||
vpnKey.replace("vpn://", "");
|
vpnKey.replace("vpn://", "");
|
||||||
@@ -383,7 +349,7 @@ bool ApiConfigsController::fillAvailableServices()
|
|||||||
{
|
{
|
||||||
QJsonObject apiPayload;
|
QJsonObject apiPayload;
|
||||||
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
||||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
apiPayload[configKey::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
|
||||||
@@ -407,7 +373,6 @@ bool ApiConfigsController::importServiceFromGateway()
|
|||||||
{
|
{
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
m_apiServicesModel->getCountryCode(),
|
m_apiServicesModel->getCountryCode(),
|
||||||
"",
|
"",
|
||||||
@@ -466,7 +431,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
apiConfig.value(configKey::userCountryCode).toString(),
|
apiConfig.value(configKey::userCountryCode).toString(),
|
||||||
newCountryCode,
|
newCountryCode,
|
||||||
@@ -479,10 +443,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||||
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
|
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
|
||||||
|
|
||||||
if (newCountryCode.isEmpty() && newCountryName.isEmpty() && !reloadServiceConfig) {
|
|
||||||
apiPayload.insert(configKey::isConnectEvent, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||||
|
|
||||||
@@ -566,7 +526,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
|
bool ApiConfigsController::deactivateDevice()
|
||||||
{
|
{
|
||||||
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
auto serverIndex = m_serversModel->getProcessedServerIndex();
|
||||||
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
auto serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
@@ -577,17 +537,12 @@ bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSubscriptionExpired(apiConfigObject)) {
|
if (isSubscriptionExpired(apiConfigObject)) {
|
||||||
if (isRemoveEvent) {
|
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
||||||
return true;
|
return false;
|
||||||
} else {
|
|
||||||
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
m_settings->getInstallationUuid(true),
|
m_settings->getInstallationUuid(true),
|
||||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||||
apiConfigObject.value(configKey::serverCountryCode).toString(),
|
apiConfigObject.value(configKey::serverCountryCode).toString(),
|
||||||
@@ -627,7 +582,6 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
|
|||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
uuid,
|
uuid,
|
||||||
apiConfigObject.value(configKey::userCountryCode).toString(),
|
apiConfigObject.value(configKey::userCountryCode).toString(),
|
||||||
serverCountryCode,
|
serverCountryCode,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
|
bool exportNativeConfig(const QString &serverCountryCode, const QString &fileName);
|
||||||
bool revokeNativeConfig(const QString &serverCountryCode);
|
bool revokeNativeConfig(const QString &serverCountryCode);
|
||||||
bool exportVpnKey(const QString &fileName);
|
// bool exportVpnKey(const QString &fileName);
|
||||||
void prepareVpnKeyExport();
|
void prepareVpnKeyExport();
|
||||||
void copyVpnKeyToClipboard();
|
void copyVpnKeyToClipboard();
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ public slots:
|
|||||||
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
bool updateServiceFromTelegram(const int serverIndex);
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
bool deactivateDevice(const bool isRemoveEvent);
|
bool deactivateDevice();
|
||||||
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
bool deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode);
|
||||||
|
|
||||||
bool isConfigValid();
|
bool isConfigValid();
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
#include "apiNewsController.h"
|
|
||||||
|
|
||||||
#include "core/api/apiUtils.h"
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
namespace configKey
|
|
||||||
{
|
|
||||||
constexpr char userCountryCode[] = "user_country_code";
|
|
||||||
constexpr char serviceType[] = "service_type";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiNewsController::ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
|
||||||
const QSharedPointer<ServersModel> &serversModel, QObject *parent)
|
|
||||||
: QObject(parent), m_newsModel(newsModel), m_settings(settings), m_serversModel(serversModel)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiNewsController::fetchNews(bool showError)
|
|
||||||
{
|
|
||||||
if (m_serversModel.isNull()) {
|
|
||||||
qWarning() << "ServersModel is null, skip fetchNews";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto stacks = m_serversModel->gatewayStacks();
|
|
||||||
if (stacks.isEmpty()) {
|
|
||||||
qDebug() << "No Gateway stacks, skip fetchNews";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
|
|
||||||
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
|
||||||
QJsonObject payload;
|
|
||||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
|
||||||
|
|
||||||
const QJsonObject stacksJson = stacks.toJson();
|
|
||||||
if (stacksJson.contains(configKey::userCountryCode)) {
|
|
||||||
payload.insert(configKey::userCountryCode, stacksJson.value(configKey::userCountryCode));
|
|
||||||
}
|
|
||||||
if (stacksJson.contains(configKey::serviceType)) {
|
|
||||||
payload.insert(configKey::serviceType, stacksJson.value(configKey::serviceType));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
|
|
||||||
future.then(this, [this, showError, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
|
||||||
auto [errorCode, responseBody] = result;
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit errorOccurred(errorCode, showError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
|
|
||||||
QJsonArray newsArray;
|
|
||||||
if (doc.isArray()) {
|
|
||||||
newsArray = doc.array();
|
|
||||||
} else if (doc.isObject()) {
|
|
||||||
QJsonObject obj = doc.object();
|
|
||||||
if (obj.value("news").isArray()) {
|
|
||||||
newsArray = obj.value("news").toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_newsModel->updateModel(newsArray);
|
|
||||||
emit fetchNewsFinished();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#ifndef APINEWSCONTROLLER_H
|
|
||||||
#define APINEWSCONTROLLER_H
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QSharedPointer>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "core/api/apiDefs.h"
|
|
||||||
#include "core/controllers/gatewayController.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include "ui/models/newsModel.h"
|
|
||||||
#include "ui/models/servers_model.h"
|
|
||||||
|
|
||||||
class ApiNewsController : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ApiNewsController(const QSharedPointer<NewsModel> &newsModel, const std::shared_ptr<Settings> &settings,
|
|
||||||
const QSharedPointer<ServersModel> &serversModel, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
Q_INVOKABLE void fetchNews(bool showError);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void errorOccurred(ErrorCode errorCode, bool showError);
|
|
||||||
void fetchNewsFinished();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSharedPointer<NewsModel> m_newsModel;
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // APINEWSCONTROLLER_H
|
|
||||||
@@ -82,7 +82,7 @@ void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex
|
|||||||
{
|
{
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
wait.exec();
|
||||||
|
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||||
m_settings->isStrictKillSwitchEnabled());
|
m_settings->isStrictKillSwitchEnabled());
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
if (reload) {
|
if (reload) {
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
wait.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs,
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs,
|
||||||
@@ -62,7 +62,6 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
||||||
apiPayload[configKey::authData] = authData;
|
apiPayload[configKey::authData] = authData;
|
||||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,7 @@
|
|||||||
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/qrCodeUtils.h"
|
#include "core/qrCodeUtils.h"
|
||||||
#include "core/serialization/serialization.h"
|
|
||||||
#include "core/serialization/transfer.h"
|
|
||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
|
ExportController::ExportController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ContainersModel> &containersModel,
|
||||||
const QSharedPointer<ClientManagementModel> &clientManagementModel,
|
const QSharedPointer<ClientManagementModel> &clientManagementModel,
|
||||||
@@ -252,7 +249,6 @@ void ExportController::generateCloakConfig()
|
|||||||
|
|
||||||
void ExportController::generateXrayConfig(const QString &clientName)
|
void ExportController::generateXrayConfig(const QString &clientName)
|
||||||
{
|
{
|
||||||
//Xray data
|
|
||||||
QJsonObject nativeConfig;
|
QJsonObject nativeConfig;
|
||||||
ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray, nativeConfig);
|
ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, clientName, Proto::Xray, nativeConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
@@ -262,72 +258,8 @@ void ExportController::generateXrayConfig(const QString &clientName)
|
|||||||
|
|
||||||
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
|
QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n");
|
||||||
for (const QString &line : std::as_const(lines)) {
|
for (const QString &line : std::as_const(lines)) {
|
||||||
m_config.append(line+ "\n");
|
m_config.append(line + "\n");
|
||||||
}
|
}
|
||||||
//Xray data
|
|
||||||
|
|
||||||
// Parse the Xray data to extract VLESS parameters and generate string
|
|
||||||
QString configString = QString(QJsonDocument(nativeConfig).toJson(QJsonDocument::Compact));
|
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(configString.toUtf8());
|
|
||||||
if (doc.isNull() || !doc.isObject()) {
|
|
||||||
qDebug() << "ERROR: Failed to parse config JSON";
|
|
||||||
emit exportErrorOccurred(ErrorCode::InternalError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject xrayConfig = doc.object();
|
|
||||||
QJsonArray outbounds = xrayConfig.value("outbounds").toArray();
|
|
||||||
|
|
||||||
if (outbounds.isEmpty()) {
|
|
||||||
qDebug() << "ERROR: Outbounds array is empty";
|
|
||||||
emit exportErrorOccurred(ErrorCode::InternalError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject outbound = outbounds[0].toObject();
|
|
||||||
QJsonObject settings = outbound.value("settings").toObject();
|
|
||||||
QJsonObject streamSettings = outbound.value("streamSettings").toObject();
|
|
||||||
|
|
||||||
QJsonArray vnext = settings.value("vnext").toArray();
|
|
||||||
if (vnext.isEmpty()) {
|
|
||||||
qDebug() << "ERROR: vnext array is empty";
|
|
||||||
emit exportErrorOccurred(ErrorCode::InternalError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject server = vnext[0].toObject();
|
|
||||||
QJsonArray users = server.value("users").toArray();
|
|
||||||
if (users.isEmpty()) {
|
|
||||||
qDebug() << "ERROR: users array is empty";
|
|
||||||
emit exportErrorOccurred(ErrorCode::InternalError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject user = users[0].toObject();
|
|
||||||
|
|
||||||
amnezia::serialization::VlessServerObject vlessServer;
|
|
||||||
vlessServer.address = server.value("address").toString();
|
|
||||||
vlessServer.port = server.value("port").toInt();
|
|
||||||
vlessServer.id = user.value("id").toString();
|
|
||||||
vlessServer.flow = user.value("flow").toString("xtls-rprx-vision");
|
|
||||||
vlessServer.encryption = user.value("encryption").toString("none");
|
|
||||||
|
|
||||||
vlessServer.network = streamSettings.value("network").toString("tcp");
|
|
||||||
vlessServer.security = streamSettings.value("security").toString("reality");
|
|
||||||
|
|
||||||
|
|
||||||
if (vlessServer.security == "reality") {
|
|
||||||
QJsonObject realitySettings = streamSettings.value("realitySettings").toObject();
|
|
||||||
vlessServer.serverName = realitySettings.value("serverName").toString();
|
|
||||||
vlessServer.publicKey = realitySettings.value("publicKey").toString();
|
|
||||||
vlessServer.shortId = realitySettings.value("shortId").toString();
|
|
||||||
vlessServer.fingerprint = realitySettings.value("fingerprint").toString("chrome");
|
|
||||||
vlessServer.spiderX = realitySettings.value("spiderX").toString("");
|
|
||||||
}
|
|
||||||
|
|
||||||
m_nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
|
|
||||||
|
|
||||||
|
|
||||||
emit exportConfigChanged();
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ bool ImportController::extractConfigFromData(QString data)
|
|||||||
}
|
}
|
||||||
case ConfigTypes::Invalid: {
|
case ConfigTypes::Invalid: {
|
||||||
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||||
m_configFileName.clear();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,11 +341,6 @@ void ImportController::importConfig()
|
|||||||
m_maliciousWarningText.clear();
|
m_maliciousWarningText.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImportController::clearConfigFileName()
|
|
||||||
{
|
|
||||||
m_configFileName.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
|
QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
|
||||||
{
|
{
|
||||||
QJsonObject openVpnConfig;
|
QJsonObject openVpnConfig;
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void importConfig();
|
void importConfig();
|
||||||
void clearConfigFileName();
|
|
||||||
bool extractConfigFromFile(const QString &fileName);
|
bool extractConfigFromFile(const QString &fileName);
|
||||||
bool extractConfigFromData(QString data);
|
bool extractConfigFromData(QString data);
|
||||||
bool extractConfigFromQr(const QByteArray &data);
|
bool extractConfigFromQr(const QByteArray &data);
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ namespace PageLoader
|
|||||||
PageSettingsConnection,
|
PageSettingsConnection,
|
||||||
PageSettingsDns,
|
PageSettingsDns,
|
||||||
PageSettingsApplication,
|
PageSettingsApplication,
|
||||||
PageSettingsNewsNotifications,
|
|
||||||
PageSettingsNewsDetail,
|
|
||||||
PageSettingsBackup,
|
PageSettingsBackup,
|
||||||
PageSettingsAbout,
|
PageSettingsAbout,
|
||||||
PageSettingsLogging,
|
PageSettingsLogging,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#include "settingsController.h"
|
#include "settingsController.h"
|
||||||
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QOperatingSystemVersion>
|
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#include "ui/qautostart.h"
|
#include "ui/qautostart.h"
|
||||||
#include "amnezia_application.h"
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
@@ -34,21 +32,7 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
|||||||
checkIfNeedDisableLogs();
|
checkIfNeedDisableLogs();
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
|
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged);
|
||||||
connect(AndroidController::instance(), &AndroidController::imeInsetsChanged, this, [this](int heightDp) {
|
|
||||||
m_imeHeight = heightDp;
|
|
||||||
emit imeHeightChanged(heightDp);
|
|
||||||
emit safeAreaBottomMarginChanged();
|
|
||||||
});
|
|
||||||
connect(AndroidController::instance(), &AndroidController::systemBarsInsetsChanged, this, [this](int navBarHeightDp, int statusBarHeightDp) {
|
|
||||||
m_cachedNavigationBarHeight = navBarHeightDp;
|
|
||||||
m_cachedStatusBarHeight = statusBarHeightDp;
|
|
||||||
emit safeAreaBottomMarginChanged();
|
|
||||||
emit safeAreaTopMarginChanged();
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
|
|
||||||
toggleDevGatewayEnv(m_isDevModeEnabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getPlatformName()
|
QString getPlatformName()
|
||||||
@@ -155,10 +139,6 @@ void SettingsController::clearLogs()
|
|||||||
Logger::clearLogs(false);
|
Logger::clearLogs(false);
|
||||||
Logger::clearServiceLogs();
|
Logger::clearServiceLogs();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
|
||||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
|
||||||
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsController::backupAppConfig(const QString &fileName)
|
void SettingsController::backupAppConfig(const QString &fileName)
|
||||||
@@ -443,76 +423,6 @@ bool SettingsController::isOnTv()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SettingsController::isEdgeToEdgeEnabled()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
if (!m_edgeToEdgeCached) {
|
|
||||||
m_cachedEdgeToEdgeEnabled = AndroidController::instance()->isEdgeToEdgeEnabled();
|
|
||||||
m_edgeToEdgeCached = true;
|
|
||||||
}
|
|
||||||
return m_cachedEdgeToEdgeEnabled;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int SettingsController::getStatusBarHeight()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
if (m_cachedStatusBarHeight < 0) {
|
|
||||||
m_cachedStatusBarHeight = AndroidController::instance()->getStatusBarHeight();
|
|
||||||
}
|
|
||||||
return m_cachedStatusBarHeight;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int SettingsController::getNavigationBarHeight()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
if (m_cachedNavigationBarHeight < 0) {
|
|
||||||
m_cachedNavigationBarHeight = AndroidController::instance()->getNavigationBarHeight();
|
|
||||||
}
|
|
||||||
return m_cachedNavigationBarHeight;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int SettingsController::getSafeAreaTopMargin()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
if (isEdgeToEdgeEnabled()) {
|
|
||||||
int height = getStatusBarHeight();
|
|
||||||
int result = height > 0 ? height : 40; // fallback to 40 if system returns 0
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SettingsController::getSafeAreaBottomMargin()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
if (isEdgeToEdgeEnabled()) {
|
|
||||||
if (m_imeHeight > 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int height = getNavigationBarHeight();
|
|
||||||
int result = height > 0 ? height : 56; // fallback to 56 if system returns 0
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SettingsController::getImeHeight()
|
|
||||||
{
|
|
||||||
return m_imeHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SettingsController::isHomeAdLabelVisible()
|
bool SettingsController::isHomeAdLabelVisible()
|
||||||
{
|
{
|
||||||
return m_settings->isHomeAdLabelVisible();
|
return m_settings->isHomeAdLabelVisible();
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ public:
|
|||||||
|
|
||||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||||
Q_PROPERTY(int safeAreaTopMargin READ getSafeAreaTopMargin NOTIFY safeAreaTopMarginChanged)
|
|
||||||
Q_PROPERTY(int safeAreaBottomMargin READ getSafeAreaBottomMargin NOTIFY safeAreaBottomMarginChanged)
|
|
||||||
Q_PROPERTY(int imeHeight READ getImeHeight NOTIFY imeHeightChanged)
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void toggleAmneziaDns(bool enable);
|
void toggleAmneziaDns(bool enable);
|
||||||
@@ -99,12 +96,6 @@ public slots:
|
|||||||
void toggleDevGatewayEnv(bool enabled);
|
void toggleDevGatewayEnv(bool enabled);
|
||||||
|
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
bool isEdgeToEdgeEnabled();
|
|
||||||
int getStatusBarHeight();
|
|
||||||
int getNavigationBarHeight();
|
|
||||||
int getSafeAreaTopMargin();
|
|
||||||
int getSafeAreaBottomMargin();
|
|
||||||
int getImeHeight();
|
|
||||||
|
|
||||||
bool isHomeAdLabelVisible();
|
bool isHomeAdLabelVisible();
|
||||||
void disableHomeAdLabel();
|
void disableHomeAdLabel();
|
||||||
@@ -133,10 +124,6 @@ signals:
|
|||||||
void devModeEnabled();
|
void devModeEnabled();
|
||||||
void gatewayEndpointChanged(const QString &endpoint);
|
void gatewayEndpointChanged(const QString &endpoint);
|
||||||
void devGatewayEnvChanged(bool enabled);
|
void devGatewayEnvChanged(bool enabled);
|
||||||
|
|
||||||
void imeHeightChanged(int height);
|
|
||||||
void safeAreaTopMarginChanged();
|
|
||||||
void safeAreaBottomMarginChanged();
|
|
||||||
|
|
||||||
void isHomeAdLabelVisibleChanged(bool visible);
|
void isHomeAdLabelVisibleChanged(bool visible);
|
||||||
void startMinimizedChanged();
|
void startMinimizedChanged();
|
||||||
@@ -147,12 +134,6 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
|
|
||||||
mutable int m_cachedStatusBarHeight = -1;
|
|
||||||
mutable int m_cachedNavigationBarHeight = -1;
|
|
||||||
mutable bool m_cachedEdgeToEdgeEnabled = false;
|
|
||||||
mutable bool m_edgeToEdgeCached = false;
|
|
||||||
int m_imeHeight = 0;
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
QString m_appVersion;
|
QString m_appVersion;
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
return tr("Active");
|
return tr("Active");
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>")
|
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>") : tr("Active");
|
||||||
: tr("Active");
|
|
||||||
}
|
}
|
||||||
case EndDateRole: {
|
case EndDateRole: {
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
@@ -48,7 +47,16 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount);
|
return tr("%1 out of %2").arg(m_accountInfoData.activeDeviceCount).arg(m_accountInfoData.maxDeviceCount);
|
||||||
}
|
}
|
||||||
case ServiceDescriptionRole: {
|
case ServiceDescriptionRole: {
|
||||||
return m_accountInfoData.subscriptionDescription;
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
|
return tr("Classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online "
|
||||||
|
"resources. "
|
||||||
|
"Speeds up to 200 Mbps");
|
||||||
|
} else if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
|
return tr("Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and "
|
||||||
|
"more. YouTube is not included in the free plan.");
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
||||||
@@ -93,8 +101,6 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
|
|||||||
|
|
||||||
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
|
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
|
||||||
|
|
||||||
accountInfoData.subscriptionDescription = accountInfoObject.value(apiDefs::key::subscriptionDescription).toString();
|
|
||||||
|
|
||||||
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
|
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
|
||||||
accountInfoData.supportedProtocols.push_back(protocol.toString());
|
accountInfoData.supportedProtocols.push_back(protocol.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ private:
|
|||||||
apiDefs::ConfigType configType;
|
apiDefs::ConfigType configType;
|
||||||
|
|
||||||
QStringList supportedProtocols;
|
QStringList supportedProtocols;
|
||||||
|
|
||||||
QString subscriptionDescription;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AccountInfoData m_accountInfoData;
|
AccountInfoData m_accountInfoData;
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan
|
|||||||
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
|
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Urdu: strLanguage = "اُرْدُوْ"; break;
|
case LanguageSettings::AvailableLanguageEnum::Urdu: strLanguage = "اُرْدُوْ"; break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Hindi: strLanguage = "हिन्दी"; break;
|
case LanguageSettings::AvailableLanguageEnum::Hindi: strLanguage = "हिन्दी"; break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Spanish: strLanguage = "Español"; break;
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +66,6 @@ void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum
|
|||||||
case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break;
|
case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Urdu: emit updateTranslations(QLocale::Urdu); break;
|
case LanguageSettings::AvailableLanguageEnum::Urdu: emit updateTranslations(QLocale::Urdu); break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Hindi: emit updateTranslations(QLocale::Hindi); break;
|
case LanguageSettings::AvailableLanguageEnum::Hindi: emit updateTranslations(QLocale::Hindi); break;
|
||||||
case LanguageSettings::AvailableLanguageEnum::Spanish: emit updateTranslations(QLocale::Spanish); break;
|
|
||||||
default: emit updateTranslations(QLocale::English); break;
|
default: emit updateTranslations(QLocale::English); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +83,6 @@ int LanguageModel::getCurrentLanguageIndex()
|
|||||||
case QLocale::Burmese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Burmese); break;
|
case QLocale::Burmese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Burmese); break;
|
||||||
case QLocale::Urdu: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Urdu); break;
|
case QLocale::Urdu: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Urdu); break;
|
||||||
case QLocale::Hindi: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Hindi); break;
|
case QLocale::Hindi: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Hindi); break;
|
||||||
case QLocale::Spanish: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Spanish); break;
|
|
||||||
default: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::English); break;
|
default: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::English); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +113,6 @@ LanguageSettings::AvailableLanguageEnum LanguageModel::getSystemLanguageEnum()
|
|||||||
case QLocale::Burmese: return LanguageSettings::AvailableLanguageEnum::Burmese;
|
case QLocale::Burmese: return LanguageSettings::AvailableLanguageEnum::Burmese;
|
||||||
case QLocale::Urdu: return LanguageSettings::AvailableLanguageEnum::Urdu;
|
case QLocale::Urdu: return LanguageSettings::AvailableLanguageEnum::Urdu;
|
||||||
case QLocale::Hindi: return LanguageSettings::AvailableLanguageEnum::Hindi;
|
case QLocale::Hindi: return LanguageSettings::AvailableLanguageEnum::Hindi;
|
||||||
case QLocale::Spanish: return LanguageSettings::AvailableLanguageEnum::Spanish;
|
|
||||||
case QLocale::English: return LanguageSettings::AvailableLanguageEnum::English;
|
case QLocale::English: return LanguageSettings::AvailableLanguageEnum::English;
|
||||||
default: return LanguageSettings::AvailableLanguageEnum::English;
|
default: return LanguageSettings::AvailableLanguageEnum::English;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ namespace LanguageSettings
|
|||||||
Arabic,
|
Arabic,
|
||||||
Burmese,
|
Burmese,
|
||||||
Urdu,
|
Urdu,
|
||||||
Hindi,
|
Hindi
|
||||||
Spanish
|
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(AvailableLanguageEnum)
|
Q_ENUM_NS(AvailableLanguageEnum)
|
||||||
|
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
#include "ui/models/newsModel.h"
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonValue>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
NewsModel::NewsModel(const std::shared_ptr<Settings> &settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings)
|
|
||||||
{
|
|
||||||
loadReadIds();
|
|
||||||
}
|
|
||||||
|
|
||||||
int NewsModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent);
|
|
||||||
return m_items.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant NewsModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
const NewsItem &item = m_items.at(index.row());
|
|
||||||
switch (role) {
|
|
||||||
case IdRole: return item.id;
|
|
||||||
case TitleRole: return item.title;
|
|
||||||
case ContentRole: return item.content;
|
|
||||||
case TimestampRole: return item.timestamp.toString(Qt::ISODate);
|
|
||||||
case IsReadRole: return item.read;
|
|
||||||
case IsProcessedRole: return index.row() == m_processedIndex;
|
|
||||||
default: return QVariant();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> NewsModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[IdRole] = "id";
|
|
||||||
roles[TitleRole] = "title";
|
|
||||||
roles[ContentRole] = "content";
|
|
||||||
roles[TimestampRole] = "timestamp";
|
|
||||||
roles[IsReadRole] = "read";
|
|
||||||
roles[IsProcessedRole] = "isProcessed";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NewsModel::markAsRead(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= m_items.size())
|
|
||||||
return;
|
|
||||||
if (!m_items[index].read) {
|
|
||||||
m_items[index].read = true;
|
|
||||||
m_readIds.insert(m_items[index].id);
|
|
||||||
saveReadIds();
|
|
||||||
QModelIndex idx = createIndex(index, 0);
|
|
||||||
emit dataChanged(idx, idx, { IsReadRole });
|
|
||||||
emit hasUnreadChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int NewsModel::processedIndex() const
|
|
||||||
{
|
|
||||||
return m_processedIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NewsModel::setProcessedIndex(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= m_items.size() || m_processedIndex == index)
|
|
||||||
return;
|
|
||||||
m_processedIndex = index;
|
|
||||||
emit processedIndexChanged(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NewsModel::updateModel(const QJsonArray &serverItems)
|
|
||||||
{
|
|
||||||
QList<NewsItem> updatedItems;
|
|
||||||
|
|
||||||
for (const QJsonValue &value : serverItems) {
|
|
||||||
if (!value.isObject())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
QJsonObject object = value.toObject();
|
|
||||||
|
|
||||||
NewsItem item;
|
|
||||||
item.id = object.value("id").toString();
|
|
||||||
item.title = object.value("title").toString();
|
|
||||||
item.content = object.value("content").toString();
|
|
||||||
item.timestamp = QDateTime::fromString(object.value("timestamp").toString(), Qt::ISODate);
|
|
||||||
item.read = m_readIds.contains(object.value("id").toString());
|
|
||||||
updatedItems.append(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
m_items = updatedItems;
|
|
||||||
std::sort(m_items.begin(), m_items.end(), [](const NewsItem &a, const NewsItem &b) { return a.timestamp > b.timestamp; });
|
|
||||||
endResetModel();
|
|
||||||
loadReadIds();
|
|
||||||
emit hasUnreadChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NewsModel::hasUnread() const
|
|
||||||
{
|
|
||||||
for (const NewsItem &item : m_items) {
|
|
||||||
if (!item.read)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NewsModel::loadReadIds()
|
|
||||||
{
|
|
||||||
QStringList ids = m_settings->readNewsIds();
|
|
||||||
m_readIds = QSet<QString>(ids.begin(), ids.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NewsModel::saveReadIds() const
|
|
||||||
{
|
|
||||||
m_settings->setReadNewsIds(QStringList(m_readIds.begin(), m_readIds.end()));
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#ifndef NEWSMODEL_H
|
|
||||||
#define NEWSMODEL_H
|
|
||||||
|
|
||||||
#include "settings.h"
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QSet>
|
|
||||||
#include <QString>
|
|
||||||
#include <QVector>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
struct NewsItem
|
|
||||||
{
|
|
||||||
QString id;
|
|
||||||
QString title;
|
|
||||||
QString content;
|
|
||||||
QDateTime timestamp;
|
|
||||||
bool read;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NewsModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum Roles {
|
|
||||||
IdRole = Qt::UserRole + 1,
|
|
||||||
TitleRole,
|
|
||||||
ContentRole,
|
|
||||||
TimestampRole,
|
|
||||||
IsReadRole,
|
|
||||||
IsProcessedRole
|
|
||||||
};
|
|
||||||
explicit NewsModel(const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
|
||||||
Q_INVOKABLE void markAsRead(int index);
|
|
||||||
|
|
||||||
Q_PROPERTY(int processedIndex READ processedIndex WRITE setProcessedIndex NOTIFY processedIndexChanged)
|
|
||||||
Q_PROPERTY(bool hasUnread READ hasUnread NOTIFY hasUnreadChanged)
|
|
||||||
int processedIndex() const;
|
|
||||||
void setProcessedIndex(int index);
|
|
||||||
|
|
||||||
void updateModel(const QJsonArray &items);
|
|
||||||
bool hasUnread() const;
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void processedIndexChanged(int index);
|
|
||||||
void hasUnreadChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QVector<NewsItem> m_items;
|
|
||||||
int m_processedIndex = -1;
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
|
||||||
QSet<QString> m_readIds;
|
|
||||||
void loadReadIds();
|
|
||||||
void saveReadIds() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // NEWSMODEL_H
|
|
||||||
@@ -44,8 +44,6 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
|
|||||||
|
|
||||||
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
|
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
|
||||||
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
|
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
|
||||||
|
|
||||||
connect(this, &QAbstractItemModel::modelReset, this, &ServersModel::recomputeGatewayStacks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int ServersModel::rowCount(const QModelIndex &parent) const
|
int ServersModel::rowCount(const QModelIndex &parent) const
|
||||||
@@ -158,18 +156,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
|
|||||||
QString primaryDns = server.value(config_key::dns1).toString();
|
QString primaryDns = server.value(config_key::dns1).toString();
|
||||||
return primaryDns == protocols::dns::amneziaDnsIp;
|
return primaryDns == protocols::dns::amneziaDnsIp;
|
||||||
}
|
}
|
||||||
case IsAdVisibleRole:{
|
|
||||||
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::isAdVisible).toBool(false);
|
|
||||||
}
|
|
||||||
case AdHeaderRole: {
|
|
||||||
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adHeader).toString();
|
|
||||||
}
|
|
||||||
case AdDescriptionRole: {
|
|
||||||
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adDescription).toString();
|
|
||||||
}
|
|
||||||
case AdEndpointRole: {
|
|
||||||
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -389,6 +375,7 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
|||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
|
|
||||||
|
roles[NameRole] = "serverName";
|
||||||
roles[NameRole] = "name";
|
roles[NameRole] = "name";
|
||||||
roles[ServerDescriptionRole] = "serverDescription";
|
roles[ServerDescriptionRole] = "serverDescription";
|
||||||
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
|
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
|
||||||
@@ -415,12 +402,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
|||||||
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
|
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
|
||||||
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
|
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
|
||||||
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
|
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
|
||||||
|
|
||||||
roles[IsAdVisibleRole] = "isAdVisible";
|
|
||||||
roles[AdHeaderRole] = "adHeader";
|
|
||||||
roles[AdDescriptionRole] = "adDescription";
|
|
||||||
roles[AdEndpointRole] = "adEndpoint";
|
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,68 +756,6 @@ bool ServersModel::isServerFromApi(const int serverIndex)
|
|||||||
return data(serverIndex, IsServerFromTelegramApiRole).toBool() || data(serverIndex, IsServerFromGatewayApiRole).toBool();
|
return data(serverIndex, IsServerFromTelegramApiRole).toBool() || data(serverIndex, IsServerFromGatewayApiRole).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServersModel::hasServersFromGatewayApi()
|
|
||||||
{
|
|
||||||
return !m_gatewayStacks.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServersModel::GatewayStacks::operator==(const GatewayStacks &other) const
|
|
||||||
{
|
|
||||||
return userCountryCodes == other.userCountryCodes && serviceTypes == other.serviceTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject ServersModel::GatewayStacks::toJson() const
|
|
||||||
{
|
|
||||||
QJsonObject obj;
|
|
||||||
if (!userCountryCodes.isEmpty()) {
|
|
||||||
obj.insert(configKey::userCountryCode, QJsonArray::fromStringList(userCountryCodes.values()));
|
|
||||||
}
|
|
||||||
if (!serviceTypes.isEmpty()) {
|
|
||||||
obj.insert(configKey::serviceType, QJsonArray::fromStringList(serviceTypes.values()));
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServersModel::recomputeGatewayStacks()
|
|
||||||
{
|
|
||||||
const bool wasEmpty = m_gatewayStacks.isEmpty();
|
|
||||||
GatewayStacks computed;
|
|
||||||
bool hasNewTags = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < m_servers.count(); ++i) {
|
|
||||||
if (data(i, IsServerFromGatewayApiRole).toBool()) {
|
|
||||||
const QJsonObject server = m_servers.at(i).toObject();
|
|
||||||
const QJsonObject apiConfig = server.value(configKey::apiConfig).toObject();
|
|
||||||
|
|
||||||
const QString userCountryCode = apiConfig.value(configKey::userCountryCode).toString();
|
|
||||||
const QString serviceType = apiConfig.value(configKey::serviceType).toString();
|
|
||||||
|
|
||||||
if (!userCountryCode.isEmpty()) {
|
|
||||||
if (!m_gatewayStacks.userCountryCodes.contains(userCountryCode)) {
|
|
||||||
hasNewTags = true;
|
|
||||||
}
|
|
||||||
computed.userCountryCodes.insert(userCountryCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serviceType.isEmpty()) {
|
|
||||||
if (!m_gatewayStacks.serviceTypes.contains(serviceType)) {
|
|
||||||
hasNewTags = true;
|
|
||||||
}
|
|
||||||
computed.serviceTypes.insert(serviceType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_gatewayStacks = std::move(computed);
|
|
||||||
if (hasNewTags) {
|
|
||||||
emit gatewayStacksExpanded();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wasEmpty != m_gatewayStacks.isEmpty()) {
|
|
||||||
emit hasServersFromGatewayApiChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ServersModel::isApiKeyExpired(const int serverIndex)
|
bool ServersModel::isApiKeyExpired(const int serverIndex)
|
||||||
{
|
{
|
||||||
auto serverConfig = m_servers.at(serverIndex).toObject();
|
auto serverConfig = m_servers.at(serverIndex).toObject();
|
||||||
@@ -903,18 +822,3 @@ bool ServersModel::processedServerIsPremium() const
|
|||||||
{
|
{
|
||||||
return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex));
|
return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServersModel::isAdVisible()
|
|
||||||
{
|
|
||||||
return data(m_defaultServerIndex, IsAdVisibleRole).toBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ServersModel::adHeader()
|
|
||||||
{
|
|
||||||
return data(m_defaultServerIndex, AdHeaderRole).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ServersModel::adDescription()
|
|
||||||
{
|
|
||||||
return data(m_defaultServerIndex, AdDescriptionRole).toString();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,16 +10,6 @@ class ServersModel : public QAbstractListModel
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
struct GatewayStacks
|
|
||||||
{
|
|
||||||
QSet<QString> userCountryCodes;
|
|
||||||
QSet<QString> serviceTypes;
|
|
||||||
|
|
||||||
bool isEmpty() const { return userCountryCodes.isEmpty() && serviceTypes.isEmpty(); }
|
|
||||||
bool operator==(const GatewayStacks &other) const;
|
|
||||||
QJsonObject toJson() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Roles {
|
enum Roles {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
ServerDescriptionRole,
|
ServerDescriptionRole,
|
||||||
@@ -47,10 +37,6 @@ public:
|
|||||||
IsCountrySelectionAvailableRole,
|
IsCountrySelectionAvailableRole,
|
||||||
ApiAvailableCountriesRole,
|
ApiAvailableCountriesRole,
|
||||||
ApiServerCountryCodeRole,
|
ApiServerCountryCodeRole,
|
||||||
IsAdVisibleRole,
|
|
||||||
AdHeaderRole,
|
|
||||||
AdDescriptionRole,
|
|
||||||
AdEndpointRole,
|
|
||||||
|
|
||||||
HasAmneziaDns
|
HasAmneziaDns
|
||||||
};
|
};
|
||||||
@@ -66,8 +52,6 @@ public:
|
|||||||
|
|
||||||
void resetModel();
|
void resetModel();
|
||||||
|
|
||||||
GatewayStacks gatewayStacks() const { return m_gatewayStacks; }
|
|
||||||
|
|
||||||
Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged)
|
Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged)
|
||||||
Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged)
|
Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged)
|
||||||
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged)
|
Q_PROPERTY(QString defaultServerDefaultContainerName READ getDefaultServerDefaultContainerName NOTIFY defaultServerDefaultContainerChanged)
|
||||||
@@ -78,15 +62,9 @@ public:
|
|||||||
defaultServerDefaultContainerChanged)
|
defaultServerDefaultContainerChanged)
|
||||||
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged)
|
Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool hasServersFromGatewayApi READ hasServersFromGatewayApi NOTIFY hasServersFromGatewayApiChanged)
|
|
||||||
|
|
||||||
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
|
Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged)
|
||||||
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged)
|
Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool isAdVisible READ isAdVisible NOTIFY defaultServerIndexChanged)
|
|
||||||
Q_PROPERTY(QString adHeader READ adHeader NOTIFY defaultServerIndexChanged)
|
|
||||||
Q_PROPERTY(QString adDescription READ adDescription NOTIFY defaultServerIndexChanged)
|
|
||||||
|
|
||||||
bool processedServerIsPremium() const;
|
bool processedServerIsPremium() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -104,8 +82,6 @@ public slots:
|
|||||||
bool isDefaultServerHasWriteAccess();
|
bool isDefaultServerHasWriteAccess();
|
||||||
bool hasServerWithWriteAccess();
|
bool hasServerWithWriteAccess();
|
||||||
|
|
||||||
bool hasServersFromGatewayApi();
|
|
||||||
|
|
||||||
const int getServersCount();
|
const int getServersCount();
|
||||||
|
|
||||||
void setProcessedServerIndex(const int index);
|
void setProcessedServerIndex(const int index);
|
||||||
@@ -152,10 +128,6 @@ public slots:
|
|||||||
bool isApiKeyExpired(const int serverIndex);
|
bool isApiKeyExpired(const int serverIndex);
|
||||||
void removeApiConfig(const int serverIndex);
|
void removeApiConfig(const int serverIndex);
|
||||||
|
|
||||||
bool isAdVisible();
|
|
||||||
QString adHeader();
|
|
||||||
QString adDescription();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
@@ -175,9 +147,6 @@ signals:
|
|||||||
void updateApiCountryModel();
|
void updateApiCountryModel();
|
||||||
void updateApiServicesModel();
|
void updateApiServicesModel();
|
||||||
|
|
||||||
void hasServersFromGatewayApiChanged();
|
|
||||||
void gatewayStacksExpanded();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ServerCredentials serverCredentials(int index) const;
|
ServerCredentials serverCredentials(int index) const;
|
||||||
|
|
||||||
@@ -198,9 +167,6 @@ private:
|
|||||||
int m_processedServerIndex;
|
int m_processedServerIndex;
|
||||||
|
|
||||||
bool m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
|
bool m_isAmneziaDnsEnabled = m_settings->useAmneziaDns();
|
||||||
|
|
||||||
GatewayStacks m_gatewayStacks;
|
|
||||||
void recomputeGatewayStacks();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SERVERSMODEL_H
|
#endif // SERVERSMODEL_H
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import QtQuick
|
|||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
|
||||||
@@ -12,139 +13,61 @@ import "../Controls2/TextTypes"
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property real contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin
|
property real contentHeight: ad.implicitHeight + ad.anchors.topMargin + ad.anchors.bottomMargin
|
||||||
property bool isFocusable: true
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
orientation: Gradient.Horizontal
|
|
||||||
GradientStop { position: 0.0; color: AmneziaStyle.color.translucentSlateGray }
|
|
||||||
GradientStop { position: 1.0; color: AmneziaStyle.color.translucentOnyxBlack }
|
|
||||||
}
|
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: AmneziaStyle.color.onyxBlack
|
border.color: AmneziaStyle.color.goldenApricot
|
||||||
|
color: AmneziaStyle.color.transparent
|
||||||
radius: 13
|
radius: 13
|
||||||
|
|
||||||
visible: ServersModel.isAdVisible
|
visible: false
|
||||||
|
// visible: GC.isDesktop() && ServersModel.isDefaultServerFromApi
|
||||||
|
// && ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && SettingsController.isHomeAdLabelVisible
|
||||||
|
|
||||||
Keys.onTabPressed: {
|
MouseArea {
|
||||||
FocusController.nextKeyTabItem()
|
anchors.fill: parent
|
||||||
}
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
Keys.onBacktabPressed: {
|
onClicked: function() {
|
||||||
FocusController.previousKeyTabItem()
|
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl("premium"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onUpPressed: {
|
|
||||||
FocusController.nextKeyUpItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
|
||||||
FocusController.nextKeyDownItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onLeftPressed: {
|
|
||||||
FocusController.nextKeyLeftItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onRightPressed: {
|
|
||||||
FocusController.nextKeyRightItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEnterPressed: {
|
|
||||||
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
|
||||||
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: content
|
id: ad
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: 16
|
anchors.margins: 16
|
||||||
anchors.rightMargin: 12
|
|
||||||
anchors.topMargin: 12
|
|
||||||
anchors.bottomMargin: 12
|
|
||||||
spacing: 20
|
|
||||||
|
|
||||||
ColumnLayout {
|
Image {
|
||||||
|
source: "qrc:/images/controls/amnezia.svg"
|
||||||
|
sourceSize: Qt.size(36, 36)
|
||||||
|
|
||||||
|
layer {
|
||||||
|
effect: ColorOverlay {
|
||||||
|
color: AmneziaStyle.color.paleGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptionTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 4
|
Layout.rightMargin: 10
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
|
||||||
CaptionTextType {
|
text: qsTr("Amnezia Premium - for access to all websites and online resources")
|
||||||
Layout.fillWidth: true
|
color: AmneziaStyle.color.pearlGray
|
||||||
text: ServersModel.adHeader
|
|
||||||
color: AmneziaStyle.color.paleGray
|
|
||||||
font.pixelSize: 14
|
|
||||||
font.weight: 700
|
|
||||||
|
|
||||||
textFormat: Text.RichText
|
lineHeight: 18
|
||||||
|
font.pixelSize: 15
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageButtonType {
|
||||||
|
image: "qrc:/images/controls/close.svg"
|
||||||
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
|
|
||||||
|
onClicked: function() {
|
||||||
|
SettingsController.disableHomeAdLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
CaptionTextType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: ServersModel.adDescription
|
|
||||||
color: AmneziaStyle.color.mutedGray
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
lineHeight: 18
|
|
||||||
lineHeightMode: Text.FixedHeight
|
|
||||||
font.pixelSize: 14
|
|
||||||
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
implicitWidth: 40
|
|
||||||
implicitHeight: 40
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: chevronBackground
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: 12
|
|
||||||
color: AmneziaStyle.color.transparent
|
|
||||||
border.width: root.activeFocus ? 1 : 0
|
|
||||||
border.color: AmneziaStyle.color.paleGray
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
PropertyAnimation { duration: 200 }
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on border.width {
|
|
||||||
PropertyAnimation { duration: 200 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
source: "qrc:/images/controls/chevron-right.svg"
|
|
||||||
sourceSize: Qt.size(24, 24)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
hoverEnabled: true
|
|
||||||
|
|
||||||
onEntered: {
|
|
||||||
chevronBackground.color = AmneziaStyle.color.slateGray
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
chevronBackground.color = AmneziaStyle.color.transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressedChanged: {
|
|
||||||
chevronBackground.color = pressed ? AmneziaStyle.color.charcoalGray : containsMouse ? AmneziaStyle.color.slateGray : AmneziaStyle.color.transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: function() {
|
|
||||||
root.forceActiveFocus()
|
|
||||||
Qt.openUrlExternally(ServersModel.getDefaultServerData("adEndpoint"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ DrawerType2 {
|
|||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
onImplicitHeightChanged: {
|
onImplicitHeightChanged: {
|
||||||
root.expandedHeight = content.implicitHeight + 32 + SettingsController.safeAreaBottomMargin
|
root.expandedHeight = content.implicitHeight + 32
|
||||||
}
|
}
|
||||||
|
|
||||||
Header2TextType {
|
Header2TextType {
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var listView: null
|
|
||||||
property var scrollToItemTarget: null
|
|
||||||
|
|
||||||
property Connections imeConnection: Connections {
|
|
||||||
target: SettingsController
|
|
||||||
function onImeHeightChanged() {
|
|
||||||
if (root.scrollToItemTarget && SettingsController.imeHeight > 0) {
|
|
||||||
scrollTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property Timer scrollTimer: Timer {
|
|
||||||
interval: 100
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (root.scrollToItemTarget && root.listView) {
|
|
||||||
if (SettingsController.imeHeight > 0) {
|
|
||||||
var item = root.scrollToItemTarget
|
|
||||||
var itemY = item.mapToItem(root.listView.contentItem, 0, 0).y
|
|
||||||
var itemHeight = item.height
|
|
||||||
var keyboardHeight = SettingsController.imeHeight + SettingsController.safeAreaBottomMargin
|
|
||||||
var visibleHeight = root.listView.height - keyboardHeight
|
|
||||||
|
|
||||||
var desiredTopOffset = visibleHeight * 0.25
|
|
||||||
var targetContentY = itemY - desiredTopOffset
|
|
||||||
|
|
||||||
if (targetContentY < 0) {
|
|
||||||
targetContentY = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxContentY = root.listView.contentHeight - root.listView.height
|
|
||||||
if (targetContentY > maxContentY) {
|
|
||||||
targetContentY = maxContentY
|
|
||||||
}
|
|
||||||
|
|
||||||
root.listView.contentY = targetContentY
|
|
||||||
root.scrollToItemTarget = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToItem(item) {
|
|
||||||
scrollToItemTarget = item
|
|
||||||
scrollTimer.restart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -49,22 +49,6 @@ Item {
|
|||||||
return drawerContent.state === stateName
|
return drawerContent.state === stateName
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Qt.application
|
|
||||||
|
|
||||||
function onStateChanged() {
|
|
||||||
if (Qt.application.state !== Qt.ApplicationActive) {
|
|
||||||
if (dragArea.drag.active) {
|
|
||||||
dragArea.drag.target = null
|
|
||||||
dragArea.drag.target = drawerContent
|
|
||||||
}
|
|
||||||
if (isOpened && !isCollapsedStateActive()) {
|
|
||||||
root.closeTriggered()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: PageController
|
target: PageController
|
||||||
|
|
||||||
|
|||||||
@@ -74,18 +74,6 @@ Item {
|
|||||||
FocusController.nextKeyRightItem()
|
FocusController.nextKeyRightItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Qt.application
|
|
||||||
|
|
||||||
function onStateChanged() {
|
|
||||||
if (Qt.application.state !== Qt.ApplicationActive) {
|
|
||||||
if (!menu.isClosed) {
|
|
||||||
menu.closeTriggered()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
implicitWidth: rootButtonContent.implicitWidth
|
implicitWidth: rootButtonContent.implicitWidth
|
||||||
implicitHeight: rootButtonContent.implicitHeight
|
implicitHeight: rootButtonContent.implicitHeight
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Popup {
|
|||||||
|
|
||||||
leftMargin: 25
|
leftMargin: 25
|
||||||
rightMargin: 25
|
rightMargin: 25
|
||||||
bottomMargin: 70 + SettingsController.safeAreaBottomMargin
|
bottomMargin: 70
|
||||||
|
|
||||||
width: parent.width - leftMargin - rightMargin
|
width: parent.width - leftMargin - rightMargin
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ Item {
|
|||||||
|
|
||||||
property string buttonText
|
property string buttonText
|
||||||
property string buttonImageSource
|
property string buttonImageSource
|
||||||
property string buttonImageColor: AmneziaStyle.color.midnightBlack
|
|
||||||
property string buttonBackgroundColor: AmneziaStyle.color.paleGray
|
|
||||||
property string buttonHoveredColor: AmneziaStyle.color.lightGray
|
|
||||||
property var clickedFunc
|
property var clickedFunc
|
||||||
|
|
||||||
property alias textField: textField
|
property alias textField: textField
|
||||||
@@ -51,7 +48,7 @@ Item {
|
|||||||
Keys.onUpPressed: {
|
Keys.onUpPressed: {
|
||||||
FocusController.nextKeyUpItem()
|
FocusController.nextKeyUpItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
Keys.onDownPressed: {
|
||||||
FocusController.nextKeyDownItem()
|
FocusController.nextKeyDownItem()
|
||||||
}
|
}
|
||||||
@@ -70,7 +67,7 @@ Item {
|
|||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
Behavior on border.color {
|
Behavior on border.color {
|
||||||
PropertyAnimation { duration: 100 }
|
PropertyAnimation { duration: 200 }
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -197,14 +194,6 @@ Item {
|
|||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.NoFocus
|
||||||
text: root.buttonText
|
text: root.buttonText
|
||||||
leftImageSource: root.buttonImageSource
|
leftImageSource: root.buttonImageSource
|
||||||
leftImageColor: root.buttonImageColor
|
|
||||||
|
|
||||||
defaultColor: root.buttonBackgroundColor
|
|
||||||
hoveredColor: root.buttonHoveredColor
|
|
||||||
pressedColor: root.buttonHoveredColor
|
|
||||||
disabledColor: AmneziaStyle.color.transparent
|
|
||||||
|
|
||||||
borderWidth: 0
|
|
||||||
|
|
||||||
anchors.top: content.top
|
anchors.top: content.top
|
||||||
anchors.bottom: content.bottom
|
anchors.bottom: content.bottom
|
||||||
@@ -212,7 +201,7 @@ Item {
|
|||||||
|
|
||||||
height: content.implicitHeight
|
height: content.implicitHeight
|
||||||
width: content.implicitHeight
|
width: content.implicitHeight
|
||||||
squareLeftSide: false
|
squareLeftSide: true
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
if (root.clickedFunc && typeof root.clickedFunc === "function") {
|
if (root.clickedFunc && typeof root.clickedFunc === "function") {
|
||||||
|
|||||||
@@ -28,7 +28,5 @@ QtObject {
|
|||||||
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
readonly property color cloudyGray: Qt.rgba(215/255, 216/255, 219/255, 0.65)
|
||||||
readonly property color pearlGray: '#EAEAEC'
|
readonly property color pearlGray: '#EAEAEC'
|
||||||
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
readonly property color translucentRichBrown: Qt.rgba(99/255, 51/255, 3/255, 0.26)
|
||||||
readonly property color translucentSlateGray: Qt.rgba(85/255, 86/255, 92/255, 0.13)
|
|
||||||
readonly property color translucentOnyxBlack: Qt.rgba(28/255, 29/255, 33/255, 0.13)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ PageType {
|
|||||||
|
|
||||||
BaseHeaderType {
|
BaseHeaderType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 20 + SettingsController.safeAreaTopMargin
|
Layout.topMargin: 20
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
|
|||||||
@@ -20,21 +20,6 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Qt.application
|
|
||||||
|
|
||||||
function onStateChanged() {
|
|
||||||
if (Qt.application.state !== Qt.ApplicationActive) {
|
|
||||||
if (drawer.isOpened) {
|
|
||||||
drawer.closeTriggered()
|
|
||||||
}
|
|
||||||
if (homeSplitTunnelingDrawer.isOpened) {
|
|
||||||
homeSplitTunnelingDrawer.closeTriggered()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
objectName: "pageControllerConnections"
|
objectName: "pageControllerConnections"
|
||||||
|
|
||||||
@@ -83,9 +68,19 @@ PageType {
|
|||||||
objectName: "homeColumnLayout"
|
objectName: "homeColumnLayout"
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 12 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 12
|
||||||
anchors.bottomMargin: 16
|
anchors.bottomMargin: 16
|
||||||
|
|
||||||
|
AdLabel {
|
||||||
|
id: adLabel
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: adLabel.contentHeight
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 22
|
||||||
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: loggingButton
|
id: loggingButton
|
||||||
objectName: "loggingButton"
|
objectName: "loggingButton"
|
||||||
@@ -114,34 +109,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
|
||||||
id: devGatewayButton
|
|
||||||
objectName: "devGatewayButton"
|
|
||||||
|
|
||||||
property bool isDevGatewayEnabled: SettingsController.isDevGatewayEnv
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
implicitHeight: 36
|
|
||||||
|
|
||||||
defaultColor: AmneziaStyle.color.transparent
|
|
||||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
|
||||||
pressedColor: AmneziaStyle.color.sheerWhite
|
|
||||||
disabledColor: AmneziaStyle.color.mutedGray
|
|
||||||
textColor: AmneziaStyle.color.mutedGray
|
|
||||||
borderWidth: 0
|
|
||||||
|
|
||||||
visible: SettingsController.isDevModeEnabled && isDevGatewayEnabled
|
|
||||||
text: qsTr("Dev gateway enabled")
|
|
||||||
|
|
||||||
Keys.onEnterPressed: this.clicked()
|
|
||||||
Keys.onReturnPressed: this.clicked()
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
PageController.goToPage(PageEnum.PageDevMenu)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectButton {
|
ConnectButton {
|
||||||
id: connectButton
|
id: connectButton
|
||||||
objectName: "connectButton"
|
objectName: "connectButton"
|
||||||
@@ -194,16 +161,6 @@ PageType {
|
|||||||
parent: root
|
parent: root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AdLabel {
|
|
||||||
id: adLabel
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: adLabel.contentHeight
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
Layout.topMargin: 22
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
@@ -31,11 +31,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SmartScroll {
|
|
||||||
id: smartScroll
|
|
||||||
listView: listView
|
|
||||||
}
|
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
@@ -85,13 +80,6 @@ PageType {
|
|||||||
clientMtu = textField.text
|
clientMtu = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(mtuTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,12 +97,6 @@ PageType {
|
|||||||
clientJunkPacketCount = textField.text
|
clientJunkPacketCount = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(junkPacketCountTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -131,12 +113,6 @@ PageType {
|
|||||||
clientJunkPacketMinSize = textField.text
|
clientJunkPacketMinSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(junkPacketMinSizeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -153,12 +129,6 @@ PageType {
|
|||||||
clientJunkPacketMaxSize = textField.text
|
clientJunkPacketMaxSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -177,12 +147,6 @@ PageType {
|
|||||||
clientSpecialJunk1 = textField.text
|
clientSpecialJunk1 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(specialJunk1TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -201,12 +165,6 @@ PageType {
|
|||||||
clientSpecialJunk2 = textField.text
|
clientSpecialJunk2 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(specialJunk2TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -225,12 +183,6 @@ PageType {
|
|||||||
clientSpecialJunk3 = textField.text
|
clientSpecialJunk3 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(specialJunk3TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -249,12 +201,6 @@ PageType {
|
|||||||
clientSpecialJunk4 = textField.text
|
clientSpecialJunk4 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(specialJunk4TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -273,12 +219,6 @@ PageType {
|
|||||||
clientSpecialJunk5 = textField.text
|
clientSpecialJunk5 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(specialJunk5TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -297,12 +237,6 @@ PageType {
|
|||||||
clientControlledJunk1 = textField.text
|
clientControlledJunk1 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(controlledJunk1TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -321,12 +255,6 @@ PageType {
|
|||||||
clientControlledJunk2 = textField.text
|
clientControlledJunk2 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(controlledJunk2TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -345,12 +273,6 @@ PageType {
|
|||||||
clientControlledJunk3 = textField.text
|
clientControlledJunk3 = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(controlledJunk3TextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -368,12 +290,6 @@ PageType {
|
|||||||
clientSpecialHandshakeTimeout = textField.text
|
clientSpecialHandshakeTimeout = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(iTimeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Header2TextType {
|
Header2TextType {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
@@ -34,11 +34,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SmartScroll {
|
|
||||||
id: smartScroll
|
|
||||||
listView: listView
|
|
||||||
}
|
|
||||||
|
|
||||||
ListViewType {
|
ListViewType {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
@@ -86,12 +81,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(vpnAddressSubnetTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,12 +104,6 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(portTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkEmptyText: true
|
checkEmptyText: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +121,6 @@ PageType {
|
|||||||
serverJunkPacketCount = textField.text
|
serverJunkPacketCount = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(junkPacketCountTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -160,12 +137,6 @@ PageType {
|
|||||||
serverJunkPacketMinSize = textField.text
|
serverJunkPacketMinSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(junkPacketMinSizeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -182,12 +153,6 @@ PageType {
|
|||||||
serverJunkPacketMaxSize = textField.text
|
serverJunkPacketMaxSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(junkPacketMaxSizeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -204,12 +169,6 @@ PageType {
|
|||||||
serverInitPacketJunkSize = textField.text
|
serverInitPacketJunkSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(initPacketJunkSizeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -226,12 +185,6 @@ PageType {
|
|||||||
serverResponsePacketJunkSize = textField.text
|
serverResponsePacketJunkSize = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(responsePacketJunkSizeTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AwgTextField {
|
// AwgTextField {
|
||||||
@@ -280,12 +233,6 @@ PageType {
|
|||||||
serverInitPacketMagicHeader = textField.text
|
serverInitPacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(initPacketMagicHeaderTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -302,12 +249,6 @@ PageType {
|
|||||||
serverResponsePacketMagicHeader = textField.text
|
serverResponsePacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(responsePacketMagicHeaderTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -324,12 +265,6 @@ PageType {
|
|||||||
serverUnderloadPacketMagicHeader = textField.text
|
serverUnderloadPacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(underloadPacketMagicHeaderTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AwgTextField {
|
AwgTextField {
|
||||||
@@ -346,12 +281,6 @@ PageType {
|
|||||||
serverTransportPacketMagicHeader = textField.text
|
serverTransportPacketMagicHeader = textField.text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textField.onActiveFocusChanged: {
|
|
||||||
if (textField.activeFocus) {
|
|
||||||
smartScroll.scrollToItem(transportPacketMagicHeaderTextField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if(backButton.enabled && backButton.activeFocus) {
|
if(backButton.enabled && backButton.activeFocus) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ PageType {
|
|||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
|
anchors.topMargin: 20
|
||||||
|
|
||||||
onFocusChanged: {
|
onFocusChanged: {
|
||||||
if (this.activeFocus) {
|
if (this.activeFocus) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user