Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD

This commit is contained in:
vkamn
2025-09-02 15:14:16 +08:00
171 changed files with 8835 additions and 7397 deletions

View File

@@ -358,7 +358,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -402,6 +402,67 @@ jobs:
path: deploy/build/client/DefaultVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-Android:

View File

@@ -1,8 +1,9 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT DefaultVPN)
set(DEFAULTVPN_VERSION 4.8.10.0)
project(${PROJECT} VERSION 1.0.4.4
project(${PROJECT} VERSION ${DEFAULTVPN_VERSION}
DESCRIPTION "DefaultVPN"
HOMEPAGE_URL "https://dfvpn.com/"
)
@@ -11,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2090)
set(APP_ANDROID_VERSION_CODE 2093)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -31,13 +32,19 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)

View File

@@ -9,17 +9,17 @@
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -6,16 +6,16 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT DefaultVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -53,6 +52,9 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
@@ -110,6 +112,15 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
@@ -139,7 +150,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -158,7 +169,6 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
endif()
if(LINUX AND NOT ANDROID)
@@ -166,8 +176,7 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -178,7 +187,9 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND NOT IOS)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
@@ -199,7 +210,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -214,7 +225,6 @@ if(NOT IOS AND NOT ANDROID)
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})

View File

@@ -12,6 +12,7 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -21,6 +22,8 @@
#include "platforms/ios/QRCodeReaderBase.h"
#include "protocols/qml_register_protocols.h"
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{
@@ -48,8 +51,17 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteLater", Qt::QueuedConnection);
}
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (!m_vpnConnectionThread.wait(5000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait();
}
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
@@ -63,15 +75,28 @@ void AmneziaApplication::init()
const QUrl url(QStringLiteral("qrc:/ui/qml/DefaultVpn/main.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -167,7 +192,7 @@ bool AmneziaApplication::parseCommands()
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
@@ -179,9 +204,8 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("DefaultVPNInstance");
QLocalServer::removeServer(serverName);
@@ -198,6 +222,22 @@ void AmneziaApplication::startLocalServer()
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;

View File

@@ -7,9 +7,9 @@
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#include <QGuiApplication>
#else
#include <QApplication>
#include <QApplication>
#endif
#include <QClipboard>
@@ -20,9 +20,9 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
@@ -60,6 +60,8 @@ private:
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
import java.time.ZoneOffset
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -135,8 +137,8 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}

View File

@@ -27,9 +27,15 @@ if(WIN32)
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif()
elseif(APPLE AND NOT IOS)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")

View File

@@ -14,7 +14,7 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USERNOTIFICATIONS}
${FW_USER_NOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
@@ -35,6 +35,8 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -53,4 +55,3 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")

168
client/cmake/macos_ne.cmake Normal file
View File

@@ -0,0 +1,168 @@
message("Client ==> MacOS NE build")
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"
)

View File

@@ -39,7 +39,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -89,12 +89,26 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/macos_util.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp

View File

@@ -131,7 +131,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
#endif

View File

@@ -8,7 +8,7 @@
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX)
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif

View File

@@ -261,6 +261,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
return true;
#elif defined(Q_OS_IOS)
// Standard iOS build (without Network Extension limitations)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
@@ -269,7 +270,23 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Cloak: return true;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default: return false;
default:
return false;
}
#elif defined(MACOS_NE)
// macOS build using Network Extension hide OpenVPN-based containers
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::OpenVpn:
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks:
return false;
default:
return false;
}
#elif defined(Q_OS_MAC)
switch (c) {

View File

@@ -23,7 +23,7 @@ namespace
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTime();
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now;
}

View File

@@ -120,6 +120,9 @@ void CoreController::initControllers()
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
connect(m_installController.get(), &InstallController::profileCleared,
m_protocolsModel.get(), &ProtocolsModel::updateModel);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
@@ -230,7 +233,7 @@ void CoreController::initSignalHandlers()
void CoreController::initNotificationHandler()
{
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -242,7 +245,10 @@ void CoreController::initNotificationHandler()
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -279,6 +285,7 @@ void CoreController::updateTranslator(const QLocale &locale)
m_engine->retranslate();
emit translationsUpdated();
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
}
void CoreController::initErrorMessagesHandler()

View File

@@ -5,6 +5,10 @@
#include <QQmlContext>
#include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/systemtray_notificationhandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
@@ -44,7 +48,7 @@
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/notificationhandler.h"
#endif
@@ -61,6 +65,7 @@ public:
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
private:
void initModels();
@@ -92,7 +97,7 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif

View File

@@ -60,8 +60,9 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo
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(QString(endpoint).arg(m_gatewayEndpoint));
request.setUrl(QString(endpoint).arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
@@ -122,8 +123,9 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
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_gatewayEndpoint));
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
@@ -344,11 +346,14 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QEventLoop wait;
QList<QSslError> sslErrors;
QByteArray responseBody;
for (const QString &proxyUrl : proxyUrls) {
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl, QNetworkReply *reply,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
QEventLoop wait;
QList<QSslError> sslErrors;
qDebug() << "go to the next proxy endpoint";
reply->deleteLater(); // delete the previous reply
reply = requestFunction(endpoint.arg(proxyUrl));
@@ -358,6 +363,50 @@ void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *repl
wait.exec();
if (replyProcessingFunction(reply, sslErrors)) {
return true;
}
return false;
};
if (m_proxyUrl.isEmpty()) {
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
for (const QString &proxyUrl : proxyUrls) {
request.setUrl(proxyUrl + "lmbd-health");
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) {
reply->deleteLater();
m_proxyUrl = proxyUrl;
if (!m_proxyUrl.isEmpty()) {
break;
}
} else {
reply->deleteLater();
}
}
}
if (!m_proxyUrl.isEmpty()) {
if (bypassFunction(endpoint, m_proxyUrl, reply, requestFunction, replyProcessingFunction)) {
return;
}
}
for (const QString &proxyUrl : proxyUrls) {
if (bypassFunction(endpoint, proxyUrl, reply, requestFunction, replyProcessingFunction)) {
m_proxyUrl = proxyUrl;
break;
}
}

View File

@@ -32,6 +32,8 @@ private:
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
inline static QString m_proxyUrl;
};
#endif // GATEWAYCONTROLLER_H

View File

@@ -120,6 +120,7 @@ namespace amnezia
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
// QFile errors
OpenError = 1200,

View File

@@ -77,6 +77,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;

View File

@@ -23,7 +23,7 @@
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
@@ -390,7 +390,7 @@ QString NetworkUtilities::getGatewayAndIface()
close(sock);
return gateway_address;
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};

View File

@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_primaryDnsServer.isNull()) {
if (!m_primaryDnsServer.isEmpty()) {
QStringList dnsServers;
dnsServers.append(m_primaryDnsServer);
if (!m_secondaryDnsServer.isNull()) {
if (!m_secondaryDnsServer.isEmpty()) {
dnsServers.append(m_secondaryDnsServer);
}
// If the DNS is not the Gateway, it's a user defined DNS

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,6 +1,68 @@
{
"info" : {
"author" : "xcode",
"version" : 1
"images": [
{
"idiom": "mac",
"size": "16x16",
"scale": "1x",
"filename": "16.png"
},
{
"idiom": "mac",
"size": "16x16",
"scale": "2x",
"filename": "16@2x.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "1x",
"filename": "32.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "2x",
"filename": "32@2x.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "1x",
"filename": "128.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "2x",
"filename": "128@2x.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "1x",
"filename": "256.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "2x",
"filename": "256@2x.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "1x",
"filename": "512.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "2x",
"filename": "512@2x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>NSCameraUsageDescription</key>
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIcons</key>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Amnezia VPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>WireGuard config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>conf</string>
<string>cfg</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>OpenVPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>ovpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>AmneziaVPN backup file</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.backup-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>backup</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Amnezia VPN config</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<string>org.amnezia.AmneziaVPN.backup-config</string>
</array>
</dict>
</array>
<key>NSExtensions</key>
<array>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -2,34 +2,40 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-settings</string>
<string>relay</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,138 @@
enable_language(Swift)
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
# MACOSX_BUNDLE YES
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
)
if(DEPLOY)
message("DEPLOY is ON")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
)
else()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
target_sources(AmneziaVPNNetworkExtension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
message(${CLIENT_ROOT_DIR})
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)

View File

@@ -2,41 +2,30 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-settings</string>
<string>relay</string>
<string>packet-tunnel-provider</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
<string>app-proxy-provider</string>
</array>
<key>keychain-access-groups</key>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
<string>allow-vpn</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.private.network.socket-delegate</key>
<true/>
</dict>
</plist>

View File

@@ -3,27 +3,32 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>DefaultVPNNetworkExtension</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>DefaultVPNNetworkExtension</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>org.amnezia.DefaultVPN.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>DefaultVPNNetworkExtension</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<string>${APPLE_PROJECT_VERSION}</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>DefaultVPNNetworkExtension</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
@@ -31,5 +36,11 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.DefaultVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.DefaultVPN</string>
</dict>
</plist>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
@@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
// init function definition in C
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);

View File

@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION

View File

@@ -15,7 +15,7 @@
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
bool isAnotherInstanceRunning()
{
QLocalSocket socket;
@@ -45,7 +45,7 @@ int main(int argc, char *argv[])
AmneziaApplication app(argc, argv);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
return app.exec();

View File

@@ -73,7 +73,7 @@ extension PacketTunnelProvider {
startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow)
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter?.transportStatistics.bytesIn

View File

@@ -112,9 +112,19 @@ extension PacketTunnelProvider {
}
}
let lastHandshakeString = settingsDictionary["last_handshake_time_sec"]
let lastHandshake: Int64
if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) {
lastHandshake = handshakeValue
} else {
lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec`
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0",
"last_handshake_time_sec": lastHandshake
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))

View File

@@ -1,3 +1,4 @@
#if !MACOS_NE
#include "QRCodeReaderBase.h"
#import <UIKit/UIKit.h>
@@ -108,3 +109,19 @@ void QRCodeReader::startReading() {
void QRCodeReader::stopReading() {
[m_qrCodeReader stopReading];
}
#else
#include "QRCodeReaderBase.h"
QRCodeReader::QRCodeReader()
{
}
QRect QRCodeReader::cameraSize() {
return QRect();
}
void QRCodeReader::startReading() {}
void QRCodeReader::stopReading() {}
void QRCodeReader::setCameraSize(QRect) {}
#endif

View File

@@ -1,5 +1,6 @@
#if !MACOS_NE
#import <UIKit/UIKit.h>
#endif
@interface QIOSApplicationDelegate
@end

View File

@@ -5,7 +5,7 @@
@implementation QIOSApplicationDelegate (DefaultVPNDelegate)
#if !MACOS_NE
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
@@ -57,5 +57,5 @@
}
return NO;
}
#endif
@end

View File

@@ -1,3 +1,13 @@
#if MACOS_NE
public func toggleScreenshots(_ isEnabled: Bool) {
}
class ScreenProtection {
}
#else
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
@@ -90,3 +100,4 @@ struct ProtectionPair {
textField.removeFromSuperview()
}
}
#endif

View File

@@ -46,6 +46,7 @@ public:
void disconnectVpn();
void vpnStatusDidChange(void *pNotification);
void vpnConfigurationDidChange(void *pNotification);
void getBackendLogs(std::function<void(const QString &)> &&callback);

View File

@@ -27,6 +27,7 @@ const char* MessageKey::isOnDemand = "is-on-demand";
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
#if !MACOS_NE
static UIViewController* getViewController() {
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
@@ -36,6 +37,7 @@ static UIViewController* getViewController() {
}
return nil;
}
#endif
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
switch (status) {
@@ -249,6 +251,21 @@ void IosController::checkStatus()
sendVpnExtensionMessage(message, [&](NSDictionary* response){
uint64_t txBytes = [response[@"tx_bytes"] intValue];
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
uint64_t last_handshake_time_sec = 0;
#if !MACOS_NE
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
} else {
qDebug() << "Key last_handshake_time_sec is missing or null";
}
if (last_handshake_time_sec < 0) {
disconnectVpn();
qDebug() << "Invalid handshake time, disconnecting VPN.";
}
#endif
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
@@ -803,14 +820,14 @@ bool IosController::shareText(const QStringList& filesToSend) {
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
[sharingItems addObject:logFileUrl];
}
#if !MACOS_NE
UIViewController *qtController = getViewController();
if (!qtController) return;
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
#endif
__block bool isAccepted = false;
#if !MACOS_NE
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
isAccepted = completed;
emit finished();
@@ -823,6 +840,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
popController.sourceRect = CGRectMake(100, 100, 100, 100);
}
#endif
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();
@@ -831,6 +849,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
}
QString IosController::openFile() {
#if !MACOS_NE
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
@@ -841,8 +860,9 @@ QString IosController::openFile() {
[qtController presentViewController:documentPicker animated:YES completion:nil];
#endif
__block QString filePath;
#if !MACOS_NE
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
if (path) {
filePath = QString::fromUtf8(path.UTF8String);
@@ -851,7 +871,7 @@ QString IosController::openFile() {
}
emit finished();
};
#endif
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();

View File

@@ -1,7 +1,11 @@
#import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NETunnelProviderSession.h>
#import <Foundation/Foundation.h>
#if !MACOS_NE
#include <UIKit/UIKit.h>
#endif
#include <Security/Security.h>
class IosController;
@@ -17,9 +21,10 @@ class IosController;
@end
typedef void (^DocumentPickerClosedCallback)(NSString *path);
#if !MACOS_NE
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
@end
#endif

View File

@@ -26,7 +26,8 @@
@end
@implementation DocumentPickerDelegate
#if !MACOS_NE
@implementation DocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
for (NSURL *url in urls) {
@@ -42,4 +43,5 @@
}
}
@end
@end
#endif

View File

@@ -6,6 +6,8 @@
#import <UserNotifications/UserNotifications.h>
#import <Foundation/Foundation.h>
#if !MACOS_NE
#import <UIKit/UIKit.h>
@interface IOSNotificationDelegate
@@ -87,3 +89,86 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
}
}];
}
#else
// Removed the UIResponder and UIApplicationDelegate references as these are not available in macOS
@interface IOSNotificationDelegate
: NSObject <UNUserNotificationCenterDelegate> {
IOSNotificationHandler* m_iosNotificationHandler;
}
@end
@implementation IOSNotificationDelegate
- (id)initWithObject:(IOSNotificationHandler*)notification {
self = [super init]; // Removed `super init` as it refers to UIResponder, which is iOS specific
if (self) {
m_iosNotificationHandler = notification;
}
return self;
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler {
Q_UNUSED(center)
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
didReceiveNotificationResponse:(UNNotificationResponse*)response
withCompletionHandler:(void (^)())completionHandler {
Q_UNUSED(center)
Q_UNUSED(response)
completionHandler();
}
@end
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError* _Nullable error) {
Q_UNUSED(granted);
if (!error) {
m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this];
}
}];
}
IOSNotificationHandler::~IOSNotificationHandler() { }
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
if (!m_delegate) {
return;
}
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = title.toNSString();
content.body = message.toNSString();
content.sound = [UNNotificationSound defaultSound];
int timerSec = timerMsec / 1000;
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:trigger];
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = (id<UNUserNotificationCenterDelegate>)m_delegate;
[center addNotificationRequest:request
withCompletionHandler:^(NSError* _Nullable error) {
if (error) {
NSLog(@"Local Notification failed");
}
}];
}
#endif

View File

@@ -30,7 +30,6 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent)
Ikev2Protocol::~Ikev2Protocol()
{
qDebug() << "IpsecProtocol::~IpsecProtocol()";
disconnect_vpn();
Ikev2Protocol::stop();
}
@@ -38,7 +37,7 @@ void Ikev2Protocol::stop()
{
setConnectionState(Vpn::ConnectionState::Disconnecting);
{
if (! disconnect_vpn() ){
if (!disconnect_vpn()){
qDebug()<<"We don't disconnect";
setConnectionState(Vpn::ConnectionState::Error);
}
@@ -311,7 +310,9 @@ bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Ikev2Protocol::disconnect_vpn(){
if ( hRasConn != nullptr ){
if ( RasHangUp(hRasConn) != ERROR_SUCCESS)
auto ret = RasHangUp(hRasConn);
qDebug() << "RasHangUp " << ret;
if (ret != ERROR_SUCCESS)
return false;
}
QThread::msleep(3000);

View File

@@ -192,7 +192,7 @@ namespace amnezia
constexpr char defaultPort[] = "51820";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";
@@ -212,7 +212,7 @@ namespace amnezia
namespace awg
{
constexpr char defaultPort[] = "55424";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";

View File

@@ -4,7 +4,7 @@
#include "core/errorstrings.h"
#include "vpnprotocol.h"
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#include "openvpnovercloakprotocol.h"
#include "openvpnprotocol.h"
#include "shadowsocksvpnprotocol.h"
@@ -109,7 +109,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);

View File

@@ -129,7 +129,7 @@
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Components/AddSitePanel.qml</file>
<file>ui/qml/Config/GlobalConfig.qml</file>
@@ -230,6 +230,7 @@
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
<file>ui/qml/Pages2/PageShare.qml</file>
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>ui/qml/Pages2/PageShareConnection.qml</file>
<file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Components/RenameServerDrawer.qml</file>
<file>ui/qml/Controls2/ListViewType.qml</file>
@@ -275,6 +276,7 @@
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/DefaultVpn/Components/BlackButtonWithBorder.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
</qresource>
<qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file>

View File

@@ -1,7 +1,7 @@
FROM alpine:3.15
LABEL maintainer="DefaultVPN"
ARG XRAY_RELEASE="v1.8.6"
ARG XRAY_RELEASE="v25.8.3"
RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz
RUN apk --update upgrade --no-cache

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,7 @@ namespace
constexpr char uuid[] = "installation_uuid";
constexpr char osVersion[] = "os_version";
constexpr char appVersion[] = "app_version";
constexpr char appLanguage[] = "app_language";
constexpr char userCountryCode[] = "user_country_code";
constexpr char serverCountryCode[] = "server_country_code";
@@ -43,6 +44,9 @@ namespace
constexpr char authData[] = "auth_data";
constexpr char config[] = "config";
constexpr char subscription[] = "subscription";
constexpr char endDate[] = "end_date";
}
struct ProtocolData
@@ -168,7 +172,7 @@ namespace
auto clientProtocolConfig =
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
//TODO looks like this block can be removed after v1 configs EOL
// TODO looks like this block can be removed after v1 configs EOL
serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount);
serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize);
@@ -228,6 +232,19 @@ namespace
return ErrorCode::NoError;
}
bool isSubscriptionExpired(const QJsonObject &apiConfig)
{
auto subscription = apiConfig.value(configKey::subscription).toObject();
if (subscription.isEmpty()) {
return false;
}
auto subscriptionEndDate = subscription.value(configKey::endDate).toString();
if (apiUtils::isSubscriptionExpired(subscriptionEndDate)) {
return true;
}
return false;
}
}
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
@@ -247,6 +264,11 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
@@ -282,6 +304,11 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
@@ -327,6 +354,7 @@ bool ApiConfigsController::fillAvailableServices()
{
QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType();
apiPayload[configKey::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
@@ -402,6 +430,11 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfig)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
@@ -509,6 +542,11 @@ bool ApiConfigsController::deactivateDevice()
return true;
}
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getInstallationUuid(true),
@@ -544,6 +582,11 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
return true;
}
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
uuid,

View File

@@ -1,6 +1,6 @@
#include "connectionController.h"
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -32,8 +32,9 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
void ConnectionController::openConnection()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
{
emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
return;
}

View File

@@ -4,6 +4,12 @@
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include "logger.h"
namespace {
Logger logger("FocusController");
}
FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
: QObject { parent },
m_engine { engine },
@@ -85,7 +91,7 @@ void FocusController::dropRootObject(QObject *object)
dropListView();
setFocusOnDefaultItem();
} else {
qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
logger.warning() << "TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
}
}
@@ -101,7 +107,7 @@ void FocusController::reload(Direction direction)
QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top());
if (!rootObject) {
qCritical() << "No ROOT OBJECT found!";
logger.error() << "No ROOT OBJECT found!";
resetRootObject();
dropListView();
return;
@@ -113,7 +119,7 @@ void FocusController::reload(Direction direction)
direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore);
if (m_focusChain.empty()) {
qWarning() << "Focus chain is empty!";
logger.warning() << "Focus chain is empty!";
resetRootObject();
dropListView();
return;
@@ -131,7 +137,7 @@ void FocusController::nextItem(Direction direction)
}
if (m_focusChain.empty()) {
qWarning() << "There are no items to navigate";
logger.warning() << "There are no items to navigate";
setFocusOnDefaultItem();
return;
}
@@ -149,7 +155,7 @@ void FocusController::nextItem(Direction direction)
const auto focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(focusedItemIndex));
if (focusedItem == nullptr) {
qWarning() << "Failed to get item to focus on. Setting focus on default";
logger.warning() << "Failed to get item to focus on. Setting focus on default";
setFocusOnDefaultItem();
return;
}

View File

@@ -19,7 +19,7 @@
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <CoreFoundation/CoreFoundation.h>
#endif
@@ -595,7 +595,7 @@ void ImportController::startDecodingQr()
m_totalQrCodeChunksCount = 0;
m_receivedQrCodeChunksCount = 0;
#if defined Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
m_isQrCodeProcessed = true;
#endif
#if defined Q_OS_ANDROID

View File

@@ -403,9 +403,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
const auto &protocols = ContainerProps::protocolsForContainer(container);
for (const auto &protocol : protocols) {
QJsonObject containerConfig;
if (protocol == mainProto) {
// for Multiprotocols (OpenVPN over SS, OpenVPN over Cloak)
bool shouldProcessProtocol = false;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
shouldProcessProtocol = true;
} else {
shouldProcessProtocol = (protocol == mainProto);
}
if (shouldProcessProtocol) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
@@ -550,14 +560,97 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
qDebug() << siteName;
containerConfig.insert(config_key::site, siteName);
} else if (protocol == Proto::OpenVpn) {
QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
protocols::openvpn::serverConfigPath, errorCode);
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) {
continue;
} else {
QStringList parts = trimmedLine.split(" ");
if (parts.count() >= 2) {
QString key = parts[0];
QString value = parts.mid(1).join(" ");
serverConfigMap.insert(key, value);
}
}
}
QString serverValue = serverConfigMap.value("server");
if (!serverValue.isEmpty()) {
QStringList serverParts = serverValue.split(" ");
if (serverParts.count() >= 1) {
containerConfig[config_key::subnet_address] = serverParts[0];
}
}
bool ncpDisable = serverConfig.contains("ncp-disable");
containerConfig[config_key::ncp_disable] = ncpDisable;
bool tlsAuth = serverConfig.contains("tls-auth");
containerConfig[config_key::tls_auth] = tlsAuth;
bool blockOutsideDns = serverConfig.contains("block-outside-dns");
containerConfig[config_key::block_outside_dns] = blockOutsideDns;
QString cipher = serverConfigMap.value("cipher");
if (!cipher.isEmpty()) {
containerConfig[config_key::cipher] = cipher;
}
QString hash = serverConfigMap.value("auth");
if (!hash.isEmpty()) {
containerConfig[config_key::hash] = hash;
}
} else if (protocol == Proto::Cloak) {
QString cloakConfig = serverController->getTextFileFromContainer(container, credentials,
"/opt/amnezia/cloak/ck-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(cloakConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject cloakConfigObj = doc.object();
QString site = cloakConfigObj.value("RedirAddr").toString();
if (!site.isEmpty()) {
containerConfig[config_key::site] = site;
}
} else {
qDebug() << "Failed to parse main loop Cloak JSON config";
}
} else if (protocol == Proto::ShadowSocks) {
QString shadowsocksConfig = serverController->getTextFileFromContainer(container, credentials,
"/opt/amnezia/shadowsocks/ss-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject ssConfigObj = doc.object();
QString cipher = ssConfigObj.value("method").toString();
if (!cipher.isEmpty()) {
containerConfig[config_key::cipher] = cipher;
}
} else {
qDebug() << "Failed to parse main loop Shadowsocks JSON config";
}
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
if (shouldProcessProtocol) {
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
}
installedContainers.insert(container, config);
}
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
if (torOrDnsRegMatch.hasMatch()) {
@@ -721,6 +814,8 @@ void InstallController::clearCachedProfile(QSharedPointer<ServerController> serv
m_clientManagementModel->revokeClient(containerConfig, container, serverCredentials, serverIndex, serverController);
emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerProps::containerHumanNames().value(container)));
QJsonObject updatedConfig = m_settings->containerConfig(serverIndex, container);
emit profileCleared(updatedConfig);
}
QRegularExpression InstallController::ipAddressPortRegExp()

View File

@@ -83,6 +83,8 @@ signals:
void noInstalledContainers();
void profileCleared(const QJsonObject &config);
private:
void installServer(const DockerContainer container, const QMap<DockerContainer, QJsonObject> &installedContainers,
const ServerCredentials &serverCredentials, const QSharedPointer<ServerController> &serverController,

View File

@@ -37,7 +37,7 @@ void ListViewFocusController::viewAtCurrentIndex() const
}
case Section::Delegate: {
QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index
Q_ARG(int, 2)); // PositionMode (0 = Visible)
Q_ARG(int, 6)); // PositionMode (0 = Beginning; 1 = Center; 2 = End; 3 = Visible; 4 = Contain; 5 = SnapPosition)
break;
}
case Section::Footer: {
@@ -112,6 +112,7 @@ void ListViewFocusController::previousDelegate()
case Section::Default: {
if (hasFooter()) {
m_currentSection = Section::Footer;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
@@ -127,9 +128,11 @@ void ListViewFocusController::previousDelegate()
case Section::Delegate: {
if (m_delegateIndex > 0) {
setDelegateIndex(m_delegateIndex - 1);
viewAtCurrentIndex();
break;
} else if (hasHeader()) {
m_currentSection = Section::Header;
viewAtCurrentIndex();
break;
}
[[fallthrough]];
@@ -137,6 +140,7 @@ void ListViewFocusController::previousDelegate()
case Section::Header: {
m_isReturnNeeded = true;
m_currentSection = Section::Default;
viewAtCurrentIndex();
break;
}
default: {
@@ -275,7 +279,7 @@ bool ListViewFocusController::isFirstFocusItemInListView() const
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
}
case Section::Header: {
isFirstFocusItemInDelegate();
return isFirstFocusItemInDelegate();
}
case Section::Default: {
return true;

View File

@@ -1,8 +1,11 @@
#include "pageController.h"
#include "utils/converter.h"
#include "core/errorstrings.h"
#if defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -25,8 +28,12 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
#endif
#if defined Q_OS_MACX
connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); });
connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); });
connect(this, &PageController::raiseMainWindow, []() {
setDockIconVisible(true);
});
connect(this, &PageController::hideMainWindow, []() {
setDockIconVisible(false);
});
#endif
connect(this, qOverload<ErrorCode>(&PageController::showErrorMessage), this, &PageController::onShowErrorMessage);
@@ -56,14 +63,11 @@ QString PageController::getPagePath(PageLoader::PageEnum page)
void PageController::closeWindow()
{
#ifdef Q_OS_ANDROID
// On mobile platforms, quit app on close; on desktop, just hide window
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
qApp->quit();
#else
if (m_serversModel->getServersCount() == 0) {
qApp->quit();
} else {
emit hideMainWindow();
}
emit hideMainWindow();
#endif
}
@@ -114,7 +118,7 @@ void PageController::showOnStartup()
} else {
#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
emit hideMainWindow();
#elif defined Q_OS_MACX
#elif defined(Q_OS_MACX)
setDockIconVisible(false);
#endif
}

View File

@@ -40,8 +40,9 @@ namespace PageLoader
PageSettingsApiInstructions,
PageSettingsApiNativeConfigs,
PageSettingsApiDevices,
PageSettingsApiSubscriptionKey,
PageSettingsKillSwitchExceptions,
PageServiceSftpSettings,
PageServiceTorWebsiteSettings,
PageServiceDnsSettings,
@@ -73,6 +74,7 @@ namespace PageLoader
PageProtocolAwgClientSettings,
PageShareFullAccess,
PageShareConnection,
PageDevMenu
};
@@ -125,6 +127,8 @@ signals:
void goToPageViewConfig();
void goToPageSettingsServerServices();
void goToPageSettingsBackup();
void goToShareConnectionPage(QString headerText, QString configContentHeaderText, QString configCaption, QString configExtension,
QString configFileName);
void closePage();

View File

@@ -10,7 +10,7 @@
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <DefaultVPN-Swift.h>
#endif
@@ -35,6 +35,23 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
#endif
}
QString getPlatformName()
{
#if defined(Q_OS_WINDOWS)
return "Windows";
#elif defined(Q_OS_ANDROID)
return "Android";
#elif defined(Q_OS_LINUX)
return "Linux";
#elif defined(Q_OS_MACX)
return "MacOS";
#elif defined(Q_OS_IOS)
return "iOS";
#else
return "Unknown";
#endif
}
void SettingsController::toggleAmneziaDns(bool enable)
{
m_settings->setUseAmneziaDns(enable);
@@ -76,7 +93,7 @@ bool SettingsController::isLoggingEnabled()
void SettingsController::toggleLogging(bool enable)
{
m_settings->setSaveLogs(enable);
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS)
DefaultVPN::toggleLogging(enable);
#endif
if (enable == true) {
@@ -130,15 +147,22 @@ void SettingsController::backupAppConfig(const QString &fileName)
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject config = doc.object();
config["AppPlatform"] = getPlatformName();
config["Conf/autoStart"] = Autostart::isAutostart();
config["Conf/killSwitchEnabled"] = isKillSwitchEnabled();
config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled();
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
}
void SettingsController::restoreAppConfig(const QString &fileName)
{
QByteArray data;
SystemController::readFile(fileName, data);
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
restoreAppConfigFromData(data);
}
@@ -155,21 +179,41 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
}
toggleAutoStart(autoStart);
#endif
m_serversModel->resetModel();
m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toBool();
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toString().toLower() == "true";
m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode);
#if defined(Q_OS_WINDOWS)
m_appSplitTunnelingModel->setRouteMode(static_cast<int>(Settings::AppsRouteMode::VpnAllExceptApps));
#endif
if (newConfigData.contains("AppPlatform")) { //if backup is from a new version
if (newConfigData.value("AppPlatform").toString() != getPlatformName()) {
m_appSplitTunnelingModel->clearAppsList();
}
}
m_appSplitTunnelingModel->toggleSplitTunneling(appSplittunnelingEnabled);
#endif
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toBool();
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toString().toLower() == "true";
m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode);
m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
m_settings->setAutoConnect(false);
m_settings->setStartMinimized(false);
m_settings->setKillSwitchEnabled(false);
m_settings->setStrictKillSwitchEnabled(false);
#endif
emit restoreBackupFinished();
} else {
emit changeSettingsErrorOccurred(tr("Backup file is corrupted"));
@@ -185,8 +229,7 @@ void SettingsController::clearSettings()
{
m_settings->clearSettings();
m_serversModel->resetModel();
m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
m_languageModel->changeLanguage(m_languageModel->getSystemLanguageEnum());
m_sitesModel->setRouteMode(Settings::RouteMode::VpnOnlyForwardSites);
m_sitesModel->toggleSplitTunneling(false);
@@ -198,7 +241,7 @@ void SettingsController::clearSettings()
emit changeSettingsFinished(tr("All settings have been reset to default values"));
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
DefaultVPN::clearSettings();
#endif
}

View File

@@ -136,6 +136,8 @@ private:
QString m_appVersion;
QString getPlatform();
QDateTime m_loggingDisableDate;
bool m_isDevModeEnabled = false;

View File

@@ -78,6 +78,13 @@ void SitesController::removeSite(int index)
emit finished(tr("Site removed: %1").arg(hostname));
}
void SitesController::removeSites()
{
m_sitesModel->removeSites();
emit finished(tr("Site list cleared!"));
}
void SitesController::importSites(const QString &fileName, bool replaceExisting)
{
QByteArray jsonData;

View File

@@ -19,6 +19,7 @@ public slots:
void addSite(QString hostname);
void removeSite(int index);
void removeSites();
void importSites(const QString &fileName, bool replaceExisting);
void exportSites(const QString &fileName);

View File

@@ -14,7 +14,7 @@
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#include <CoreFoundation/CoreFoundation.h>
#endif
@@ -58,8 +58,10 @@ void SystemController::saveFile(const QString &fileName, const QString &data)
const auto url = fi.absoluteDir().absolutePath();
#endif
#ifndef MACOS_NE
QDesktopServices::openUrl(url);
#endif
#endif
}
bool SystemController::readFile(const QString &fileName, QByteArray &data)

View File

@@ -31,7 +31,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
return tr("Active");
}
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("Inactive") : tr("Active");
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>") : tr("Active");
}
case EndDateRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {

View File

@@ -15,6 +15,7 @@ namespace
constexpr char serviceInfo[] = "service_info";
constexpr char serviceType[] = "service_type";
constexpr char serviceProtocol[] = "service_protocol";
constexpr char serviceDescription[] = "service_description";
constexpr char name[] = "name";
constexpr char price[] = "price";
@@ -22,6 +23,10 @@ namespace
constexpr char timelimit[] = "timelimit";
constexpr char region[] = "region";
constexpr char description[] = "description";
constexpr char cardDescription[] = "card_description";
constexpr char features[] = "features";
constexpr char availableCountries[] = "available_countries";
constexpr char storeEndpoint[] = "store_endpoint";
@@ -65,11 +70,9 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. "
"Access all websites and online resources. Speeds up to %1 Mbps.")
.arg(speed);
return apiServiceData.serviceInfo.cardDescription.arg(speed);
} else if (serviceType == serviceType::amneziaFree) {
QString description = tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
QString description = apiServiceData.serviceInfo.cardDescription;
if (!isServiceAvailable) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, "
"return to the previous screen, and try again.</a>");
@@ -78,12 +81,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
}
}
case ServiceDescriptionRole: {
if (serviceType == serviceType::amneziaPremium) {
return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. "
"Access all websites and online resources.");
} else {
return tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan.");
}
return apiServiceData.serviceInfo.description;
}
case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) {
@@ -107,13 +105,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
return apiServiceData.serviceInfo.region;
}
case FeaturesRole: {
if (serviceType == serviceType::amneziaPremium) {
return tr("");
} else {
return tr("VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. "
"Other sites will be opened from your real IP address, "
"<a href=\"%1/free\" style=\"color: #FBB26A;\">more details on the website.</a>");
}
return apiServiceData.serviceInfo.features;
}
case PriceRole: {
auto price = apiServiceData.serviceInfo.price;
@@ -125,6 +117,13 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case EndDateRole: {
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
}
case OrderRole: {
if (serviceType == serviceType::amneziaPremium) {
return 0;
} else if (serviceType == serviceType::amneziaFree) {
return 1;
}
}
}
return QVariant();
@@ -224,6 +223,7 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
roles[FeaturesRole] = "features";
roles[PriceRole] = "price";
roles[EndDateRole] = "endDate";
roles[OrderRole] = "order";
return roles;
}
@@ -234,6 +234,7 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
auto serviceType = data.value(configKey::serviceType).toString();
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
auto availableCountries = data.value(configKey::availableCountries).toArray();
auto serviceDescription = data.value(configKey::serviceDescription).toObject();
auto subscriptionObject = data.value(configKey::subscription).toObject();
@@ -244,6 +245,10 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
serviceData.serviceInfo.cardDescription = serviceDescription.value(configKey::cardDescription).toString();
serviceData.serviceInfo.description = serviceDescription.value(configKey::description).toString();
serviceData.serviceInfo.features = serviceDescription.value(configKey::features).toString();
serviceData.type = serviceType;
serviceData.protocol = serviceProtocol;

View File

@@ -20,7 +20,8 @@ public:
RegionRole,
FeaturesRole,
PriceRole,
EndDateRole
EndDateRole,
OrderRole
};
explicit ApiServicesModel(QObject *parent = nullptr);
@@ -58,6 +59,10 @@ private:
QString region;
QString price;
QString description;
QString features;
QString cardDescription;
QJsonObject object;
};

View File

@@ -26,12 +26,12 @@ QVariant AppSplitTunnelingModel::data(const QModelIndex &index, int role) const
return QVariant();
switch (role) {
case AppPathRole: {
return m_apps.at(index.row()).appName;
}
default: {
return true;
}
case AppPathRole: {
return m_apps.at(index.row()).appName;
}
default: {
return true;
}
}
return QVariant();
@@ -59,6 +59,13 @@ void AppSplitTunnelingModel::removeApp(QModelIndex index)
endRemoveRows();
}
void AppSplitTunnelingModel::clearAppsList() {
beginResetModel();
m_apps.clear();
m_settings->setVpnApps(m_currentRouteMode, m_apps);
endResetModel();
}
int AppSplitTunnelingModel::getRouteMode()
{
return m_currentRouteMode;

View File

@@ -29,6 +29,7 @@ public:
public slots:
bool addApp(const InstalledAppInfo &appInfo);
void removeApp(QModelIndex index);
void clearAppsList();
int getRouteMode();
void setRouteMode(int routeMode);

View File

@@ -101,6 +101,23 @@ QString LanguageModel::getCurrentLanguageName()
return m_availableLanguages[getCurrentLanguageIndex()].name;
}
LanguageSettings::AvailableLanguageEnum LanguageModel::getSystemLanguageEnum()
{
QLocale locale = QLocale::system();
switch (locale.language()) {
case QLocale::Russian: return LanguageSettings::AvailableLanguageEnum::Russian;
case QLocale::Chinese: return LanguageSettings::AvailableLanguageEnum::China_cn;
case QLocale::Ukrainian: return LanguageSettings::AvailableLanguageEnum::Ukrainian;
case QLocale::Persian: return LanguageSettings::AvailableLanguageEnum::Persian;
case QLocale::Arabic: return LanguageSettings::AvailableLanguageEnum::Arabic;
case QLocale::Burmese: return LanguageSettings::AvailableLanguageEnum::Burmese;
case QLocale::Urdu: return LanguageSettings::AvailableLanguageEnum::Urdu;
case QLocale::Hindi: return LanguageSettings::AvailableLanguageEnum::Hindi;
case QLocale::English: return LanguageSettings::AvailableLanguageEnum::English;
default: return LanguageSettings::AvailableLanguageEnum::English;
}
}
QString LanguageModel::getCurrentSiteUrl(const QString &path)
{
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
@@ -110,3 +127,13 @@ QString LanguageModel::getCurrentSiteUrl(const QString &path)
default: return QString("https://amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path)));
}
}
QString LanguageModel::getCurrentDocsUrl(const QString &path)
{
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
switch (language) {
case LanguageSettings::AvailableLanguageEnum::Russian:
return "https://storage.googleapis.com/amnezia/docs" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path)));
default: return QString("https://docs.amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path)));
}
}

View File

@@ -46,6 +46,7 @@ public:
};
LanguageModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
LanguageSettings::AvailableLanguageEnum getSystemLanguageEnum();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
@@ -60,6 +61,7 @@ public slots:
int getLineHeightAppend();
QString getCurrentLanguageName();
QString getCurrentSiteUrl(const QString &path = "");
QString getCurrentDocsUrl(const QString &path = "");
signals:
void updateTranslations(const QLocale &locale);

View File

@@ -191,14 +191,14 @@ QJsonObject AwgConfigModel::getConfig()
jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount];
jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize];
jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize];
jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1];
jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2];
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3];
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4];
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5];
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1];
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2];
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3];
jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1].toString().trimmed();
jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2].toString().trimmed();
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3].toString().trimmed();
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4].toString().trimmed();
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5].toString().trimmed();
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1].toString().trimmed();
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2].toString().trimmed();
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3].toString().trimmed();
jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout];
m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());

View File

@@ -4,7 +4,7 @@
#include "core/controllers/serverController.h"
#include "core/networkUtilities.h"
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <DefaultVPN-Swift.h>
#endif
@@ -782,7 +782,7 @@ void ServersModel::removeApiConfig(const int serverIndex)
{
auto serverConfig = getServerConfig(serverIndex);
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
QString vpncName = QString("%1 (%2) %3")
.arg(serverConfig[config_key::description].toString())
.arg(serverConfig[config_key::hostName].toString())

View File

@@ -83,6 +83,16 @@ void SitesModel::removeSite(QModelIndex index)
endRemoveRows();
}
void SitesModel::removeSites()
{
beginResetModel();
m_settings->removeAllVpnSites(m_currentRouteMode);
fillSites();
endResetModel();
}
int SitesModel::getRouteMode()
{
return m_currentRouteMode;

View File

@@ -28,6 +28,7 @@ public slots:
bool addSite(const QString &hostname, const QString &ip);
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
void removeSite(QModelIndex index);
void removeSites();
int getRouteMode();
void setRouteMode(int routeMode);

View File

@@ -0,0 +1,36 @@
#ifndef NE_NOTIFICATION_HANDLER_H
#define NE_NOTIFICATION_HANDLER_H
#include "notificationhandler.h"
#include <QMenu>
#include <QAction>
class MacOSStatusIcon;
class NEStatusBarNotificationHandler : public NotificationHandler {
Q_OBJECT
public:
explicit NEStatusBarNotificationHandler(QObject* parent);
~NEStatusBarNotificationHandler() override;
void setConnectionState(Vpn::ConnectionState state) override;
void onTranslationsUpdated() override;
protected:
void notify(Message type, const QString& title,
const QString& message, int timerMsec) override;
private:
void buildMenu();
QMenu m_menu;
MacOSStatusIcon* m_statusIcon;
QAction* m_actionShow;
QAction* m_actionConnect;
QAction* m_actionDisconnect;
QAction* m_actionVisitWebsite;
QAction* m_actionQuit;
};
#endif // NE_NOTIFICATION_HANDLER_H

View File

@@ -11,18 +11,12 @@
# include "systemtray_notificationhandler.h"
#endif
// static
NotificationHandler* NotificationHandler::create(QObject* parent) {
#if defined(Q_OS_IOS)
return new IOSNotificationHandler(parent);
#else
# if defined(Q_OS_LINUX)
//if (LinuxSystemTrayNotificationHandler::requiredCustomImpl()) {
// return new LinuxSystemTrayNotificationHandler(parent);
//}
# endif
return new SystemTrayNotificationHandler(parent);
#endif
}

View File

@@ -10,8 +10,7 @@ import ProtocolEnum 1.0
import "../Controls2"
import "../Controls2/TextTypes"
ListView {
ListViewType {
id: menuContent
property var rootWidth
@@ -21,13 +20,6 @@ ListView {
anchors.top: parent.top
anchors.bottom: parent.bottom
clip: true
snapMode: ListView.SnapToItem
ScrollBar.vertical: ScrollBarType {}
property bool isFocusable: true
ButtonGroup {
id: containersRadioButtonGroup
}

View File

@@ -1,97 +1,97 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
anchors.fill: parent
expandedHeight: parent.height * 0.9
expandedStateContent: ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
headerText: qsTr("Split tunneling")
descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
}
LabelWithButtonType {
id: splitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
text: qsTr("Split tunneling on the server")
descriptionText: qsTr("Enabled \nCan't be disabled for current server")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
}
LabelWithButtonType {
id: siteBasedSplitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Site-based split tunneling")
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
}
LabelWithButtonType {
id: appSplitTunnelingSwitch
visible: isAppSplitTinnelingEnabled
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: isAppSplitTinnelingEnabled
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
anchors.fill: parent
expandedHeight: parent.height * 0.9
expandedStateContent: ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
headerText: qsTr("Split tunneling")
descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
}
LabelWithButtonType {
id: splitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
text: qsTr("Split tunneling on the server")
descriptionText: qsTr("Enabled \nCan't be disabled for current server")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
}
LabelWithButtonType {
id: siteBasedSplitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Site-based split tunneling")
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
}
LabelWithButtonType {
id: appSplitTunnelingSwitch
visible: isAppSplitTinnelingEnabled
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: isAppSplitTinnelingEnabled
}
}
}

View File

@@ -57,7 +57,7 @@ DrawerType2 {
headerText: qsTr("Choose application")
}
ListView {
ListViewType {
id: listView
Layout.fillWidth: true
@@ -66,11 +66,6 @@ DrawerType2 {
Layout.rightMargin: 16
Layout.leftMargin: 16
clip: true
interactive: true
property bool isFocusable: true
model: SortFilterProxyModel {
id: proxyInstalledAppsModel
sourceModel: installedAppsModel
@@ -81,44 +76,35 @@ DrawerType2 {
}
}
ScrollBar.vertical: ScrollBarType {}
ButtonGroup {
id: buttonGroup
}
delegate: Item {
implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight
delegate: ColumnLayout {
width: listView.width
ColumnLayout {
id: delegateContent
RowLayout {
CheckBoxType {
Layout.fillWidth: true
anchors.fill: parent
RowLayout {
CheckBoxType {
Layout.fillWidth: true
text: appName
checked: isAppSelected
onCheckedChanged: {
installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked)
}
}
Image {
source: "image://installedAppImage/" + appIcon
sourceSize.width: 24
sourceSize.height: 24
Layout.rightMargin: 48
text: appName
checked: isAppSelected
onCheckedChanged: {
installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked)
}
}
DividerType {}
Image {
source: "image://installedAppImage/" + appIcon
sourceSize.width: 24
sourceSize.height: 24
Layout.rightMargin: 48
}
}
DividerType {}
}
}
}

View File

@@ -49,7 +49,7 @@ DrawerType2 {
}
}
ListView {
ListViewType {
id: listView
anchors.top: backButtonLayout.bottom
@@ -57,14 +57,8 @@ DrawerType2 {
anchors.right: parent.right
anchors.bottom: parent.bottom
property bool isFocusable: true
property int selectedIndex: LanguageModel.currentLanguageIndex
clip: true
reuseItems: true
ScrollBar.vertical: ScrollBarType {}
model: LanguageModel
ButtonGroup {

View File

@@ -15,7 +15,7 @@ import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
ListView {
ListViewType {
id: root
property int selectedIndex: ServersModel.defaultIndex
@@ -28,10 +28,6 @@ ListView {
model: ServersModel
ScrollBar.vertical: ScrollBarType {}
property bool isFocusable: true
Connections {
target: ServersModel
function onDefaultServerIndexChanged(serverIndex) {
@@ -39,9 +35,6 @@ ListView {
}
}
clip: true
reuseItems: true
delegate: Item {
id: menuContentDelegate
objectName: "menuContentDelegate"

View File

@@ -13,78 +13,64 @@ import "../Controls2"
import "../Controls2/TextTypes"
ListView {
ListViewType {
id: root
width: parent.width
height: root.contentItem.height
anchors.fill: parent
clip: true
reuseItems: true
delegate: ColumnLayout {
width: root.width
property bool isFocusable: false
LabelWithButtonType {
Layout.fillWidth: true
delegate: Item {
implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight
text: name
descriptionText: description
rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg"
ColumnLayout {
id: delegateContent
clickedFunction: function() {
if (isInstalled) {
var containerIndex = root.model.mapToSource(index)
ContainersModel.setProcessedContainerIndex(containerIndex)
anchors.fill: parent
LabelWithButtonType {
id: containerRadioButton
implicitWidth: parent.width
text: name
descriptionText: description
rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg"
clickedFunction: function() {
if (isInstalled) {
var containerIndex = root.model.mapToSource(index)
ContainersModel.setProcessedContainerIndex(containerIndex)
if (serviceType !== ProtocolEnum.Other) {
if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
return
}
}
switch (containerIndex) {
case ContainerEnum.Ipsec: {
if (serviceType !== ProtocolEnum.Other) {
if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
break
return
}
case ContainerEnum.Dns: {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
break
}
default: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
}
}
} else {
ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index))
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
switch (containerIndex) {
case ContainerEnum.Ipsec: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
break
}
case ContainerEnum.Dns: {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
break
}
default: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
}
}
} else {
ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index))
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
}
}
DividerType {}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
DividerType {}
}
}

View File

@@ -1,375 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
DrawerType2 {
id: root
property string headerText
property string configContentHeaderText
property string shareButtonText: qsTr("Share")
property string copyButtonText: qsTr("Copy")
property bool isSelfHostedConfig: true
property string configExtension: ".vpn"
property string configCaption: qsTr("Save DefaultVPN config")
property string configFileName: "amnezia_config"
expandedHeight: parent.height * 0.9
onClosed: {
configExtension = ".vpn"
configCaption = qsTr("Save DefaultVPN config")
configFileName = "amnezia_config"
}
expandedStateContent: Item {
implicitHeight: root.expandedHeight
Header2Type {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
anchors.leftMargin: 16
anchors.rightMargin: 16
headerText: root.headerText
}
ListView {
id: listView
anchors.top: header.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
property bool isFocusable: true
ScrollBar.vertical: ScrollBarType {}
model: 1
clip: true
reuseItems: true
header: ColumnLayout {
width: listView.width
BasicButtonType {
id: shareButton
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: root.shareButtonText
leftImageSource: "qrc:/images/controls/share-2.svg"
clickedFunc: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = configFileName + configExtension
} else {
fileName = SystemController.getFileName(configCaption,
qsTr("Config files (*" + configExtension + ")"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName,
true,
configExtension)
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
ExportController.exportConfig(fileName)
PageController.showBusyIndicator(false)
}
}
}
BasicButtonType {
id: copyConfigTextButton
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: root.copyButtonText
leftImageSource: "qrc:/images/controls/copy.svg"
Keys.onReturnPressed: { copyConfigTextButton.clicked() }
Keys.onEnterPressed: { copyConfigTextButton.clicked() }
}
BasicButtonType {
id: copyNativeConfigStringButton
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: false
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Copy config string")
leftImageSource: "qrc:/images/controls/copy.svg"
KeyNavigation.tab: showSettingsButton
}
BasicButtonType {
id: showSettingsButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: root.isSelfHostedConfig
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Show connection settings")
clickedFunc: function() {
configContentDrawer.openTriggered()
}
}
DrawerType2 {
id: configContentDrawer
parent: root.parent
anchors.fill: parent
expandedHeight: parent.height * 0.9
expandedStateContent: Item {
id: configContentContainer
implicitHeight: configContentDrawer.expandedHeight
Connections {
target: copyNativeConfigStringButton
function onClicked() {
nativeConfigString.selectAll()
nativeConfigString.copy()
nativeConfigString.select(0, 0)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
Connections {
target: copyConfigTextButton
function onClicked() {
configText.selectAll()
configText.copy()
configText.select(0, 0)
PageController.showNotificationMessage(qsTr("Copied"))
header.forceActiveFocus()
}
}
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
backButtonFunction: function() { configContentDrawer.closeTriggered() }
}
FlickableType {
anchors.top: backButton.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin
ColumnLayout {
id: configContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
Header2Type {
id: configContentHeader
Layout.fillWidth: true
Layout.topMargin: 16
headerText: root.configContentHeaderText
}
TextField {
id: nativeConfigString
visible: false
text: ExportController.nativeConfigString
onTextChanged: {
copyNativeConfigStringButton.visible = nativeConfigString.text !== ""
}
}
TextArea {
id: configText
Layout.fillWidth: true
Layout.topMargin: 16
Layout.bottomMargin: 16
padding: 0
leftPadding: 0
height: 24
readOnly: true
activeFocusOnTab: false
color: AmneziaStyle.color.paleGray
selectionColor: AmneziaStyle.color.richBrown
selectedTextColor: AmneziaStyle.color.paleGray
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
text: ExportController.config
wrapMode: Text.Wrap
background: Rectangle {
color: AmneziaStyle.color.transparent
}
}
}
}
}
}
}
delegate: ColumnLayout {
width: listView.width
property bool isQrCodeVisible: root.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : ApiConfigsController.qrCodesCount > 0
Rectangle {
id: qrCodeContainer
Layout.fillWidth: true
Layout.preferredHeight: width
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isQrCodeVisible
color: "white"
Image {
anchors.fill: parent
smooth: false
source: root.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") :
(isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "")
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Timer {
property int index: 0
interval: 1000
running: isQrCodeVisible
repeat: true
onTriggered: {
if (isQrCodeVisible) {
index++
let qrCodesCount = root.isSelfHostedConfig ? ExportController.qrCodesCount : ApiConfigsController.qrCodesCount
if (index >= qrCodesCount) {
index = 0
}
parent.source = root.isSelfHostedConfig ? ExportController.qrCodes[index] : ApiConfigsController.qrCodes[index]
}
}
}
Behavior on source {
PropertyAnimation { duration: 200 }
}
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isQrCodeVisible
horizontalAlignment: Text.AlignHCenter
text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"")
}
}
}
}
}

View File

@@ -1,220 +1,210 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Button {
id: root
property string hoveredColor: AmneziaStyle.color.lightGray
property string defaultColor: AmneziaStyle.color.paleGray
property string disabledColor: AmneziaStyle.color.charcoalGray
property string pressedColor: AmneziaStyle.color.mutedGray
property string textColor: AmneziaStyle.color.midnightBlack
property string borderColor: AmneziaStyle.color.paleGray
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
property int borderFocusedWidth: 1
property string leftImageSource
property string rightImageSource
property string leftImageColor: textColor
property bool changeLeftImageSize: true
property bool squareLeftSide: false
property FlickableType parentFlickable
property var clickedFunc
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56
hoverEnabled: true
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)
}
}
}
background: Rectangle {
id: focusBorder
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: parent
radius: 16
Rectangle {
id: background
anchors.fill: focusBorder
anchors.margins: root.activeFocus ? 2 : 0
radius: root.activeFocus ? 14 : 16
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
border.color: borderColor
border.width: borderWidth
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
visible: root.squareLeftSide
z: 1
width: parent.radius
height: parent.radius
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
}
MouseArea {
anchors.fill: focusBorder
enabled: false
cursorShape: Qt.PointingHandCursor
}
contentItem: Item {
anchors.fill: focusBorder
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.centerIn: parent
Image {
id: leftImage
source: root.leftImageSource
visible: root.leftImageSource === "" ? false : true
layer {
enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay {
color: leftImageColor
}
}
Component.onCompleted: {
if (root.changeLeftImageSize) {
leftImage.Layout.preferredHeight = 20
leftImage.Layout.preferredWidth = 20
}
}
}
ButtonTextType {
id: buttonText
color: root.textColor
text: root.text
visible: root.text === "" ? false : true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
Image {
Layout.preferredHeight: 20
Layout.preferredWidth: 20
source: root.rightImageSource
visible: root.rightImageSource === "" ? false : true
layer {
enabled: true
effect: ColorOverlay {
color: textColor
}
}
}
}
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
Keys.onReturnPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Button {
id: root
property string hoveredColor: AmneziaStyle.color.lightGray
property string defaultColor: AmneziaStyle.color.paleGray
property string disabledColor: AmneziaStyle.color.charcoalGray
property string pressedColor: AmneziaStyle.color.mutedGray
property string textColor: AmneziaStyle.color.midnightBlack
property string borderColor: AmneziaStyle.color.paleGray
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
property int borderFocusedWidth: 1
property string leftImageSource
property string rightImageSource
property string leftImageColor: textColor
property bool changeLeftImageSize: true
property bool squareLeftSide: false
property var clickedFunc
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56
hoverEnabled: true
background: Rectangle {
id: focusBorder
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: parent
radius: 16
Rectangle {
id: background
anchors.fill: focusBorder
anchors.margins: root.activeFocus ? 2 : 0
radius: root.activeFocus ? 14 : 16
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
border.color: borderColor
border.width: borderWidth
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
visible: root.squareLeftSide
z: 1
width: parent.radius
height: parent.radius
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
}
MouseArea {
anchors.fill: focusBorder
enabled: false
cursorShape: Qt.PointingHandCursor
}
contentItem: Item {
anchors.fill: focusBorder
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.centerIn: parent
Image {
id: leftImage
source: root.leftImageSource
visible: root.leftImageSource === "" ? false : true
layer {
enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay {
color: leftImageColor
}
}
Component.onCompleted: {
if (root.changeLeftImageSize) {
leftImage.Layout.preferredHeight = 20
leftImage.Layout.preferredWidth = 20
}
}
}
ButtonTextType {
id: buttonText
color: root.textColor
text: root.text
visible: root.text === "" ? false : true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
Image {
Layout.preferredHeight: 20
Layout.preferredWidth: 20
source: root.rightImageSource
visible: root.rightImageSource === "" ? false : true
layer {
enabled: true
effect: ColorOverlay {
color: textColor
}
}
}
}
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
Keys.onReturnPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}

View File

@@ -1,203 +1,185 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Button {
id: root
property string headerText
property string bodyText
property string footerText
property string hoveredColor: AmneziaStyle.color.slateGray
property string defaultColor: AmneziaStyle.color.onyxBlack
property string textColor: AmneziaStyle.color.midnightBlack
property string rightImageSource
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageSource
property real textOpacity: 1.0
property alias focusItem: rightImage
property FlickableType parentFlickable
hoverEnabled: true
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
color: defaultColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
function ensureVisible(item) {
if (item.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
onFocusChanged: {
ensureVisible(root)
}
focusItem.onFocusChanged: {
root.ensureVisible(focusItem)
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.fill: parent
Image {
id: leftImage
source: leftImageSource
visible: leftImageSource !== ""
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 24
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: root.bodyText !== "" ? 0 : 16
opacity: root.textOpacity
}
CaptionTextType {
text: root.bodyText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
textFormat: Text.RichText
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: root.footerText !== "" ? 0 : 16
opacity: root.textOpacity
}
ButtonTextType {
text: root.footerText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 16
opacity: root.textOpacity
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.rightMargin: 16
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: "transparent"
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
root.clicked()
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
enabled: root.enabled
onEntered: {
backgroundRect.color = root.hoveredColor
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
backgroundRect.color = root.defaultColor
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
root.clicked()
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Button {
id: root
property string headerText
property string bodyText
property string footerText
property string hoveredColor: AmneziaStyle.color.slateGray
property string defaultColor: AmneziaStyle.color.onyxBlack
property string textColor: AmneziaStyle.color.midnightBlack
property string rightImageSource
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageSource
property real textOpacity: 1.0
property alias focusItem: rightImage
hoverEnabled: true
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
color: defaultColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.fill: parent
Image {
id: leftImage
source: leftImageSource
visible: leftImageSource !== ""
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 24
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: root.bodyText !== "" ? 0 : 16
opacity: root.textOpacity
}
CaptionTextType {
text: root.bodyText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
textFormat: Text.RichText
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: root.footerText !== "" ? 0 : 16
opacity: root.textOpacity
}
ButtonTextType {
text: root.footerText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 16
opacity: root.textOpacity
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.rightMargin: 16
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: "transparent"
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
root.clicked()
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
enabled: root.enabled
onEntered: {
backgroundRect.color = root.hoveredColor
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
backgroundRect.color = root.defaultColor
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
root.clicked()
}
}
}

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