Compare commits

...

15 Commits

Author SHA1 Message Date
Mitternacht822 6d798344d2 cleanup commented lines 2026-01-30 17:18:38 +04:00
Mitternacht822 dec1f51fd6 refactor: QR device transfer flow and UI tweaks 2026-01-29 18:51:42 +04:00
Mitternacht822 832fea3d50 refactor: transferController 2026-01-16 02:31:21 +04:00
Mitternacht822 43b51db132 transferController refactor 2026-01-14 09:21:36 +04:00
Mitternacht822 d391abb0cc temp point 2026-01-14 09:12:29 +04:00
Mitternacht822 153e33d5df refactor: chenged POST/GET methods to simple blocking request 2025-12-08 23:34:08 +04:00
Mitternacht822 1cb171698a fix: removed query from request body 2025-12-08 16:36:59 +04:00
Mitternacht822 f02128796a cleared redundant lines 2025-12-01 14:37:20 +04:00
Mitternacht822 71705f435b draft: updated version for debug 2025-11-28 12:45:50 +04:00
Mitternacht822 f821bcb01f temporary save 2025-11-17 19:20:27 +04:00
Mitternacht822 9a5e437f7b saving intermediate version 2025-10-30 15:38:30 +04:00
Mitternacht822 3d2105a439 feat(gateway): QR-based config transfer between devices 2025-09-03 20:11:22 +04:00
Nethius 16d92ddb7c fix: UI fixes after merge with d20ed4a (#1779)
* fix: ui fixes after merge with d20ed4a

* update OpenVPN settings page

* chore: page settings dns margins

---------

Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-08-11 13:40:28 +08:00
Cyril Anisimov e9d4fd8482 fix checkbox switch (#1777) 2025-08-10 11:13:58 +08:00
Yaroslav 9fdcf5ab13 feat: macos with network extension Implementation (#1468)
* There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package
ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339

* Cmake related changes

* Source code changes

* Various entitlements

* Ci-cd config update

* Resources changes

* Submodules updated

* Remove me

* QtWidget exclusion omitted

* Distribution errors fixed

* Outdated files deleted

* macos_ne cmake fixed

* fix: update provisioning profile specifiers for macOS network extension

* fix: update provisioning profile specifiers and code sign flags for macOS build

* Revert me
(temporary 3rd-build commit pointer)

* fix: Welcome screen fix

* fix: ci/cd hanging forever fix

* fix: Fixed error popup on macos on file save

* refactor: rename networkextension target to AmneziaVPNNetworkExtension in macos build configuration

* feat: add autostart support for Mac App Store builds on macOS

Fixes: QA-8

* feat: add debug logging to Autostart functionality on macOS

* Revert "feat: add autostart support for Mac App Store builds on macOS"

This reverts commit 3bd25656fb.

* feat: add platform-specific close window behavior for macOS App Store build with Network Extension

Closes: QA-12

* When the application starts with "Start minimized" enabled on macOS (especially the
sandboxed App-Store build compiled with MACOS_NE), fully hiding the window prevents it
from being restored by clicking the Dock icon. The proper behaviour is to start the
window in the *minimized* state instead. That way the window is still part of the
window list and the system automatically brings it back when the user clicks the Dock
icon, replicating the native experience.

On the other platforms we keep the old behaviour (hide the window completely and rely
on the tray icon), therefore we switch at runtime by checking the current OS.

Closes: QA-7

Closes: QA-8

* Revert "When the application starts with "Start minimized" enabled on macOS (especially the"

This reverts commit 7b0d17987c.

* feat: MACOS_NE systray menu support

* feat: add macOS notification handler and install event filter on main window

* feat: implement custom close behavior for Amnezia application on different platforms

* fix: update provisioning profile specifiers for macos builds

* fix: Fatal error in logs

CLI-216

* fix: disabled unavailable on macos ne service logs

* fix: dock icon now hides only when window is closed; menubar icon shows always

Initial state of the docker icon to be presented follows "Start minimized" setting in app settings.

* temp-fix: temporary disable all OpenVPN options of VPN on MACOS_NE since it's not working yet.

* fix: build script updated

* feat: add macOS NE build workflow to GitHub Actions

* fix: Not working Auto start toggle is hidden

* fix: Log spamming during xray connection fixed

* 3rd-prebuild points to commit that stores macos_ne universal binaries.

* fix: missing native dependency on linking stage fixed

* chore: update link to submodule

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-10 11:12:19 +08:00
91 changed files with 2683 additions and 639 deletions
+62 -1
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/AmneziaVPN.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:
+9 -3
View File
@@ -32,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)
+18 -8
View File
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -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})
+50 -10
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/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -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("AmneziaVPNInstance");
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;
+7 -5
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
+9 -3
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")
+3 -2
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
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"
)
+16 -2
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
@@ -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
+5 -4
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
+18 -1
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) {
@@ -154,6 +154,9 @@ void CoreController::initControllers()
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
m_transferController.reset(new TransferController(m_settings, m_serversModel, m_exportController.get()));
m_engine->rootContext()->setContextProperty("TransferController", m_transferController.get());
}
void CoreController::initAndroidController()
+2
View File
@@ -23,6 +23,7 @@
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/transferController.h"
#include "ui/models/allowed_dns_model.h"
#include "ui/models/containers_model.h"
@@ -145,6 +146,7 @@ private:
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
QScopedPointer<TransferController> m_transferController;
};
#endif // CORECONTROLLER_H
@@ -8,6 +8,7 @@
#include <QJsonObject>
#include <QNetworkReply>
#include <QUrl>
#include <QRegularExpression>
#include "QBlockCipher.h"
#include "QRsa.h"
+2 -2
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};
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<!-- Monitor -->
<rect x="2.5" y="3.5" width="14" height="10" rx="2" ry="2" stroke="#D7D8DB" stroke-width="1.8"/>
<line x1="9.5" y1="13.5" x2="9.5" y2="16.5" stroke="#D7D8DB" stroke-width="1.8" stroke-linecap="round"/>
<rect x="6" y="16.5" width="7" height="1.8" rx="0.9" ry="0.9" fill="#D7D8DB"/>
<!-- Phone -->
<rect x="17.5" y="6" width="4.5" height="9.5" rx="1.2" ry="1.2" stroke="#D7D8DB" stroke-width="1.8"/>
<circle cx="19.75" cy="14.2" r="0.6" fill="#D7D8DB"/>
</svg>

After

Width:  |  Height:  |  Size: 628 B

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

+65 -3
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"
}
}
+172
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>
+27 -21
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>
@@ -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>
@@ -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)
@@ -3,27 +3,32 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>org.amnezia.AmneziaVPN.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>AmneziaVPNNetworkExtension</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>AmneziaVPNNetworkExtension</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.AmneziaVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
</dict>
</plist>
@@ -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>
@@ -2,9 +2,9 @@
* 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);
@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION
+2 -2
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();
@@ -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: []))
+17
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
+2 -1
View File
@@ -1,5 +1,6 @@
#if !MACOS_NE
#import <UIKit/UIKit.h>
#endif
@interface QIOSApplicationDelegate
@end
+2 -2
View File
@@ -5,7 +5,7 @@
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
#if !MACOS_NE
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
@@ -57,5 +57,5 @@
}
return NO;
}
#endif
@end
@@ -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
+1
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);
+25 -5
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();
@@ -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
@@ -26,6 +26,7 @@
@end
#if !MACOS_NE
@implementation DocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
@@ -43,3 +44,4 @@
}
@end
#endif
@@ -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
+2 -2
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";
+2 -2
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);
+5
View File
@@ -229,6 +229,7 @@
<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/PageTransferConfigViaQR.qml</file>
<file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Components/RenameServerDrawer.qml</file>
<file>ui/qml/Controls2/ListViewType.qml</file>
@@ -236,7 +237,11 @@
<file>ui/qml/Pages2/PageSettingsApiInstructions.qml</file>
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevicesLimit.qml</file>
<file>ui/qml/Pages2/PageSettingsApiAddDeviceScan.qml</file>
<file>ui/qml/Pages2/PageSettingsApiAddDeviceConfirm.qml</file>
<file>images/controls/monitor.svg</file>
<file>images/controls/monitor-with-phone.svg</file>
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
<file>ui/qml/Components/OtpCodeDrawer.qml</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;
}
+22 -3
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
@@ -584,7 +584,26 @@ bool ImportController::decodeQrCode(const QString &code)
mInstance->m_totalQrCodeChunksCount = 0;
mInstance->m_receivedQrCodeChunksCount = 0;
}
return mInstance->parseQrCodeChunk(code);
// First, try transferController QR (plain JSON payload).
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
if (err.error == QJsonParseError::NoError && doc.isObject()) {
const QJsonObject obj = doc.object();
if (obj.contains(QStringLiteral("gw"))
&& (obj.contains(QStringLiteral("uuid")) || obj.contains(QStringLiteral("u")))) {
// это наш QR для передачи устройства
mInstance->m_isQrCodeProcessed = false;
emit mInstance->transferQrDecoded(code);
return true;
}
}
if (mInstance->parseQrCodeChunk(code)) {
return true;
}
return false;
}
#endif
@@ -595,7 +614,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
+1
View File
@@ -57,6 +57,7 @@ signals:
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
void qrDecodingFinished();
void transferQrDecoded(const QString &code);
void restoreAppConfig(const QByteArray &data);
+14 -10
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
}
+4
View File
@@ -38,6 +38,9 @@ namespace PageLoader
PageSettingsApiInstructions,
PageSettingsApiNativeConfigs,
PageSettingsApiDevices,
PageSettingsApiDevicesLimit,
PageSettingsApiAddDeviceConfirm,
PageSettingsApiAddDeviceScan,
PageSettingsApiSubscriptionKey,
PageSettingsKillSwitchExceptions,
@@ -73,6 +76,7 @@ namespace PageLoader
PageShareFullAccess,
PageShareConnection,
PageTransferConfigViaQR,
PageDevMenu
};
+9 -5
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 <AmneziaVPN-Swift.h>
#endif
@@ -93,7 +93,7 @@ bool SettingsController::isLoggingEnabled()
void SettingsController::toggleLogging(bool enable)
{
m_settings->setSaveLogs(enable);
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS)
AmneziaVPN::toggleLogging(enable);
#endif
if (enable == true) {
@@ -157,8 +157,12 @@ void SettingsController::backupAppConfig(const QString &fileName)
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);
}
@@ -237,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)
AmneziaVPN::clearSettings();
#endif
}
+3 -1
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)
@@ -0,0 +1,344 @@
#include "transferController.h"
#include <QVariant>
#include <QJsonParseError>
#include <QDebug>
#include <qeventloop.h>
#include <QNetworkProxyFactory>
#include <QNetworkProxyQuery>
#include <QUrl>
#include "core/api/apiUtils.h"
#include "core/qrCodeUtils.h"
#include "amnezia_application.h"
#include "settings.h"
#include "ui/models/servers_model.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "core/api/apiDefs.h"
#include "core/controllers/gatewayController.h"
#include "core/errorstrings.h"
namespace {
void logSystemProxiesForUrl(const QString &urlStr)
{
const QUrl url(urlStr);
const QList<QNetworkProxy> proxies = QNetworkProxyFactory::systemProxyForQuery(QNetworkProxyQuery(url));
QStringList proxyDesc;
proxyDesc.reserve(proxies.size());
for (const auto &p : proxies) {
proxyDesc << QStringLiteral("%1 %2:%3")
.arg(p.type() == QNetworkProxy::NoProxy ? QStringLiteral("NoProxy")
: p.type() == QNetworkProxy::HttpProxy ? QStringLiteral("HttpProxy")
: p.type() == QNetworkProxy::Socks5Proxy ? QStringLiteral("Socks5Proxy")
: QStringLiteral("Proxy"))
.arg(p.hostName())
.arg(p.port());
}
}
}
TransferController::TransferController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<ServersModel> &serversModel,
ExportController *exportController,
QObject *parent)
: QObject(parent), m_settings(settings), m_serversModel(serversModel), m_exportController(exportController)
{
}
void TransferController::handleImportControllerDestroyed()
{
m_importController = nullptr;
stopWaitForConfig();
}
TransferController::~TransferController() {
}
QString TransferController::buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid) const
{
QJsonObject obj;
obj["gw"] = gatewayUrl;
obj["uuid"] = uuid;
// Used on the sender side for human-friendly notifications (same style as "Active Devices" list).
#if defined(Q_OS_ANDROID)
obj["name"] = QStringLiteral("Android");
#elif defined(Q_OS_IOS)
obj["name"] = QStringLiteral("iOS");
#elif defined(Q_OS_WIN)
obj["name"] = QStringLiteral("Windows");
#elif defined(Q_OS_MACOS)
obj["name"] = QStringLiteral("macOS");
#elif defined(Q_OS_LINUX)
obj["name"] = QStringLiteral("Linux");
#else
obj["name"] = QStringLiteral("Device");
#endif
return QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::Compact));
}
void TransferController::generateNewQrCode()
{
QString gw = m_settings->getGatewayEndpoint();
if (!gw.endsWith('/')) {
gw.append('/');
}
m_currentUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
m_currentPayload = buildQrPayloadJson(gw, m_currentUuid);
auto qr = qrCodeUtils::generateQrCode(m_currentPayload.toUtf8());
const QString svg = QString::fromStdString(toSvgString(qr, 1));
m_qrCodeUrl = qrCodeUtils::svgToBase64(svg);
emit qrCodeUpdated();
emit currentUuidChanged();
emit currentPayloadChanged();
}
void TransferController::stopScanner()
{
emit scannerShouldStop();
}
QString TransferController::getCurrentApiKey(QString *vpnKeyOut) const
{
const int idx = m_serversModel ? m_serversModel->getProcessedServerIndex() : -1;
if (idx < 0 || !m_serversModel) {
return QString();
}
const QJsonObject server = m_serversModel->getServerConfig(idx);
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
const QString apiKey = authData.value(QStringLiteral("api_key")).toString();
if (vpnKeyOut) {
QString vpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
if (vpnKey.isEmpty()) {
vpnKey = apiUtils::getPremiumV1VpnKey(server);
}
*vpnKeyOut = vpnKey;
}
return apiKey;
}
void TransferController::onTransferQrScanned(const QString &code)
{
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(code.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "TransferController::onTransferQrScanned: invalid QR JSON " << err.errorString();
emit postFailed(QStringLiteral("Invalid QR JSON"));
return;
}
const QJsonObject obj = doc.object();
QString gw = obj.value("gw").toString();
const QString uuid = obj.value("uuid").toString();
if (gw.isEmpty() || uuid.isEmpty()) {
qWarning() << "TransferController::onTransferQrScanned: QR missing gw or uuid";
emit postFailed(QStringLiteral("QR missing gw or uuid"));
return;
}
if (!gw.endsWith('/')) {
gw.append('/');
}
int chosenServerIdx = -1;
QString apiKey;
QString vpnKey;
auto tryServerIndex = [&](int idx) -> bool {
if (!m_serversModel || idx < 0 || idx >= m_serversModel->getServersCount()) {
return false;
}
const QJsonObject server = m_serversModel->getServerConfig(idx);
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
const QJsonObject authData = server.value(QStringLiteral("auth_data")).toObject();
const QString candidateApiKey = authData.value(QStringLiteral("api_key")).toString();
QString candidateVpnKey = apiConfig.value(apiDefs::key::vpnKey).toString();
if (candidateVpnKey.isEmpty()) {
// Fallback for older Premium V1 configs where vpn_key may be derived.
candidateVpnKey = apiUtils::getPremiumV1VpnKey(server);
}
const bool candidateIsPremium = apiUtils::isPremiumServer(server);
const bool candidateIsFromGatewayApi = m_serversModel->data(idx, ServersModel::IsServerFromGatewayApiRole).toBool();
if (candidateApiKey.isEmpty() || candidateVpnKey.isEmpty()) {
return false;
}
if (!candidateIsPremium && !candidateIsFromGatewayApi) {
return false;
}
chosenServerIdx = idx;
apiKey = candidateApiKey;
vpnKey = candidateVpnKey;
return true;
};
if (m_serversModel) {
tryServerIndex(m_serversModel->getProcessedServerIndex());
if (chosenServerIdx < 0) {
tryServerIndex(m_serversModel->getDefaultServerIndex());
}
if (chosenServerIdx < 0) {
const int n = m_serversModel->getServersCount();
for (int i = 0; i < n; i++) {
if (tryServerIndex(i)) {
break;
}
}
}
}
if (chosenServerIdx < 0) {
qWarning() << "TransferController::onTransferQrScanned: no suitable subscription key/config found to send";
emit postFailed(QStringLiteral("No subscription key or config to send"));
return;
}
emit postStarted();
const int sendTimeoutMs = 60000;
GatewayController gatewayController(gw,
m_settings->isDevGatewayEnv(),
sendTimeoutMs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject payload;
payload.insert(QStringLiteral("uuid"), uuid);
payload.insert(QStringLiteral("api_key"), apiKey);
payload.insert(QStringLiteral("config"), vpnKey);
const QString endpoint = QStringLiteral("%1v1/sendConfig");
QByteArray responseBody;
const QString fullUrl = endpoint.arg(gw);
qDebug() << "TransferController::onTransferQrScanned: POST" << fullUrl
<< "uuid:" << uuid;
logSystemProxiesForUrl(fullUrl);
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);
qDebug() << "TransferController::onTransferQrScanned: sendConfig finished with code"
<< static_cast<int>(errorCode)
<< "response size:" << responseBody.size();
if (errorCode != ErrorCode::NoError) {
qWarning() << "TransferController::onTransferQrScanned: sendConfig failed with code" << static_cast<int>(errorCode);
emit postFailed(QStringLiteral("sendConfig failed: %1").arg(errorString(errorCode)));
return;
}
QJsonParseError parseErr;
const QJsonDocument respDoc = QJsonDocument::fromJson(responseBody, &parseErr);
if (parseErr.error == QJsonParseError::NoError && respDoc.isObject()
&& respDoc.object().value(QStringLiteral("status")).toString() == QStringLiteral("success")) {
emit postSucceeded();
stopScanner();
return;
}
qWarning() << "TransferController::onTransferQrScanned: unexpected gateway response:" << responseBody;
emit postFailed(QStringLiteral("Gateway response error"));
}
QString TransferController::qrCodeUrl() const
{
return m_qrCodeUrl;
}
void TransferController::startWaitForConfig(ImportController *importController)
{
QString gw = m_settings->getGatewayEndpoint();
if (!gw.endsWith(QLatin1Char('/'))) {
gw.append(QLatin1Char('/'));
}
const QString uuid = m_currentUuid;
if (uuid.isEmpty()) {
qWarning() << "TransferController::startWaitForConfig: no uuid";
emit waitError(QStringLiteral("No UUID"));
return;
}
m_importController = importController;
if (m_importController) {
connect(m_importController, &ImportController::destroyed,
this,
&TransferController::handleImportControllerDestroyed,
Qt::UniqueConnection);
}
const int waitTimeoutMs = 60000;
QJsonObject payload;
payload.insert(QStringLiteral("uuid"), uuid);
GatewayController gatewayController(gw,
m_settings->isDevGatewayEnv(),
waitTimeoutMs,
m_settings->isStrictKillSwitchEnabled());
const QString endpoint = QStringLiteral("%1v1/waitConfig");
QByteArray responseBody;
const QString fullUrl = endpoint.arg(gw);
logSystemProxiesForUrl(fullUrl);
const auto errorCode = gatewayController.post(endpoint, payload, responseBody);
if (errorCode != ErrorCode::NoError) {
qWarning() << "TransferController::startWaitForConfig: waitConfig failed with code" << static_cast<int>(errorCode);
emit waitError(QStringLiteral("waitConfig failed (%1)").arg(static_cast<int>(errorCode)));
return;
}
if (!m_importController) {
qWarning() << "TransferController::startWaitForConfig: import controller is null";
emit waitError(QStringLiteral("Import Controller destroyed"));
return;
}
QJsonParseError parseErr;
const QJsonDocument respDoc = QJsonDocument::fromJson(responseBody, &parseErr);
if (parseErr.error != QJsonParseError::NoError || !respDoc.isObject()) {
qWarning() << "TransferController::startWaitForConfig: invalid JSON response:" << responseBody;
emit waitError(QStringLiteral("Invalid gateway response"));
return;
}
const QJsonObject respObj = respDoc.object();
const QString status = respObj.value(QStringLiteral("status")).toString();
const QString configStr = respObj.value(QStringLiteral("config")).toString();
if (status != QStringLiteral("success")) {
qWarning() << "TransferController::startWaitForConfig: gateway status not success:" << status;
emit waitError(QStringLiteral("Gateway error"));
return;
}
if (configStr.isEmpty()) {
emit waitError(QStringLiteral("Empty config"));
return;
}
if (configStr == QStringLiteral("timeout")) {
emit waitError(QStringLiteral("Timeout"));
return;
}
if (!m_importController->extractConfigFromData(configStr)) {
qWarning() << "TransferController::startWaitForConfig: failed to parse config string";
emit waitError(QStringLiteral("Invalid config payload"));
return;
}
m_importController->importConfig();
emit configApplied();
}
void TransferController::stopWaitForConfig()
{
qDebug() << "TransferController::stopWaitForConfig: stop flag set";
}
@@ -0,0 +1,77 @@
#ifndef TRANSFERCONTROLLER_H
#define TRANSFERCONTROLLER_H
#include <QObject>
#include <QScopedPointer>
#include <QJsonObject>
#include <QJsonDocument>
#include <QUuid>
class Settings;
class ServersModel;
class ExportController;
class ImportController;
class TransferController : public QObject
{
Q_OBJECT
Q_PROPERTY(QString qrCodeUrl READ qrCodeUrl NOTIFY qrCodeUpdated)
Q_PROPERTY(QString pendingQrCode READ pendingQrCode WRITE setPendingQrCode NOTIFY pendingQrCodeChanged)
Q_PROPERTY(QString currentUuid READ currentUuid NOTIFY currentUuidChanged)
Q_PROPERTY(QString currentPayload READ currentPayload NOTIFY currentPayloadChanged)
public:
explicit TransferController(const std::shared_ptr<Settings> &settings,
const QSharedPointer<ServersModel> &serversModel,
ExportController *exportController,
QObject *parent = nullptr);
~TransferController() override;
Q_INVOKABLE void generateNewQrCode();
Q_INVOKABLE void stopScanner();
Q_INVOKABLE void onTransferQrScanned(const QString &code);
Q_INVOKABLE void setPendingQrCode(const QString &code) { m_pendingQrCode = code; emit pendingQrCodeChanged(); }
QString pendingQrCode() const { return m_pendingQrCode; }
Q_INVOKABLE void startWaitForConfig(ImportController *importController);
Q_INVOKABLE void stopWaitForConfig();
QString qrCodeUrl() const;
QString currentUuid() const { return m_currentUuid; }
QString currentPayload() const { return m_currentPayload; }
signals:
void qrCodeUpdated();
void scannerShouldStop();
void pendingQrCodeChanged();
void currentUuidChanged();
void currentPayloadChanged();
void waitError(const QString &message);
void configApplied();
void postStarted();
void postSucceeded();
void postFailed(const QString &message);
private slots:
void handleImportControllerDestroyed();
private:
QString buildQrPayloadJson(const QString &gatewayUrl, const QString &uuid) const;
QString m_pendingQrCode;
QString getCurrentApiKey(QString *vpnKeyOut = nullptr) const;
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServersModel> m_serversModel;
ExportController *m_exportController { nullptr };
ImportController *m_importController { nullptr };
QString m_qrCodeUrl;
QString m_currentUuid;
QString m_currentPayload;
};
#endif // TRANSFERCONTROLLER_H
+2 -2
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 <AmneziaVPN-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())
+36
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
+1 -7
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
}
+9 -7
View File
@@ -173,15 +173,17 @@ CheckBox {
enabled: false
}
Keys.onEnterPressed: event => handleSwitch(event)
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
Keys.onEnterPressed: {
root.checked = !root.checked
function handleSwitch(event) {
if (!event.isAutoRepeat) {
root.checked = !root.checked
root.checkedChanged()
}
event.accepted = true
}
Keys.onReturnPressed: {
root.checked = !root.checked
}
}
@@ -34,7 +34,7 @@ PageType {
ListViewType {
id: listView
anchors.top: backButtonLayout.bottom
anchors.top: backButton.bottom
anchors.bottom: saveButton.top
anchors.right: parent.right
anchors.left: parent.left
@@ -37,7 +37,7 @@ PageType {
ListViewType {
id: listView
anchors.top: backButtonLayout.bottom
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
@@ -17,428 +17,414 @@ import "../Components"
PageType {
id: root
ColumnLayout {
id: backButtonLayout
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
BackButtonType {
id: backButton
onActiveFocusChanged: {
if(backButton.enabled && backButton.activeFocus) {
listView.positionViewAtBeginning()
}
}
}
FlickableType {
id: fl
anchors.top: backButtonLayout.bottom
ListViewType {
id: listView
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
contentHeight: content.implicitHeight
anchors.right: parent.right
anchors.left: parent.left
Column {
id: content
enabled: ServersModel.isProcessedServerHasWriteAccess()
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
header: ColumnLayout {
width: listView.width
enabled: ServersModel.isProcessedServerHasWriteAccess()
BaseHeaderType {
id: header
ListView {
id: listview
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
width: parent.width
height: listview.contentItem.height
headerText: qsTr("OpenVPN Settings")
}
}
clip: true
interactive: false
model: OpenVpnConfigModel
model: OpenVpnConfigModel
delegate: ColumnLayout {
width: listView.width
delegate: Item {
id: delegateItem
spacing: 0
property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField
property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess()
TextFieldWithHeaderType {
id: vpnAddressSubnetTextField
implicitWidth: listview.width
implicitHeight: col.implicitHeight
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
ColumnLayout {
id: col
enabled: listView.enabled
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
anchors.leftMargin: 16
anchors.rightMargin: 16
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
}
}
spacing: 0
checkEmptyText: true
}
BaseHeaderType {
Layout.fillWidth: true
headerText: qsTr("OpenVPN settings")
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
TextFieldWithHeaderType {
id: vpnAddressSubnetTextField
text: qsTr("Network protocol")
}
Layout.fillWidth: true
Layout.topMargin: 32
TransportProtoSelector {
id: transportProtoSelector
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: delegateItem.isEnabled
rootWidth: root.width
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
enabled: isTransportProtoEditable
parentFlickable: fl
currentIndex: {
return transportProto === "tcp" ? 1 : 0
}
textField.onEditingFinished: {
if (textField.text !== subnetAddress) {
subnetAddress = textField.text
}
onCurrentIndexChanged: {
if (transportProto === "tcp" && currentIndex === 0) {
transportProto = "udp"
} else if (transportProto === "udp" && currentIndex === 1) {
transportProto = "tcp"
}
}
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 40
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: listView.enabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
}
}
checkEmptyText: true
}
SwitcherType {
id: autoNegotiateEncryprionSwitcher
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Auto-negotiate encryption")
checked: autoNegotiateEncryprion
onCheckedChanged: {
if (checked !== autoNegotiateEncryprion) {
autoNegotiateEncryprion = checked
}
}
}
DropDownType {
id: hashDropDown
Layout.fillWidth: true
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: !autoNegotiateEncryprionSwitcher.checked
descriptionText: qsTr("Hash")
headerText: qsTr("Hash")
drawerParent: root
listView: ListViewWithRadioButtonType {
id: hashListView
rootWidth: root.width
model: ListModel {
ListElement { name : qsTr("SHA512") }
ListElement { name : qsTr("SHA384") }
ListElement { name : qsTr("SHA256") }
ListElement { name : qsTr("SHA3-512") }
ListElement { name : qsTr("SHA3-384") }
ListElement { name : qsTr("SHA3-256") }
ListElement { name : qsTr("whirlpool") }
ListElement { name : qsTr("BLAKE2b512") }
ListElement { name : qsTr("BLAKE2s256") }
ListElement { name : qsTr("SHA1") }
}
clickedFunction: function() {
hashDropDown.text = selectedText
hash = hashDropDown.text
hashDropDown.closeTriggered()
}
Component.onCompleted: {
hashDropDown.text = hash
for (var i = 0; i < hashListView.model.count; i++) {
if (hashListView.model.get(i).name === hashDropDown.text) {
currentIndex = i
}
checkEmptyText: true
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 32
text: qsTr("Network protocol")
}
TransportProtoSelector {
id: transportProtoSelector
Layout.fillWidth: true
Layout.topMargin: 16
rootWidth: root.width
enabled: isTransportProtoEditable
currentIndex: {
return transportProto === "tcp" ? 1 : 0
}
onCurrentIndexChanged: {
if (transportProto === "tcp" && currentIndex === 0) {
transportProto = "udp"
} else if (transportProto === "udp" && currentIndex === 1) {
transportProto = "tcp"
}
}
}
TextFieldWithHeaderType {
id: portTextField
Layout.fillWidth: true
Layout.topMargin: 40
parentFlickable: fl
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
textField.validator: IntValidator { bottom: 1; top: 65535 }
textField.onEditingFinished: {
if (textField.text !== port) {
port = textField.text
}
}
checkEmptyText: true
}
SwitcherType {
id: autoNegotiateEncryprionSwitcher
Layout.fillWidth: true
Layout.topMargin: 24
parentFlickable: fl
text: qsTr("Auto-negotiate encryption")
checked: autoNegotiateEncryprion
onCheckedChanged: {
if (checked !== autoNegotiateEncryprion) {
autoNegotiateEncryprion = checked
}
}
}
DropDownType {
id: hashDropDown
Layout.fillWidth: true
Layout.topMargin: 20
enabled: !autoNegotiateEncryprionSwitcher.checked
descriptionText: qsTr("Hash")
headerText: qsTr("Hash")
drawerParent: root
listView: ListViewWithRadioButtonType {
id: hashListView
rootWidth: root.width
model: ListModel {
ListElement { name : qsTr("SHA512") }
ListElement { name : qsTr("SHA384") }
ListElement { name : qsTr("SHA256") }
ListElement { name : qsTr("SHA3-512") }
ListElement { name : qsTr("SHA3-384") }
ListElement { name : qsTr("SHA3-256") }
ListElement { name : qsTr("whirlpool") }
ListElement { name : qsTr("BLAKE2b512") }
ListElement { name : qsTr("BLAKE2s256") }
ListElement { name : qsTr("SHA1") }
}
clickedFunction: function() {
hashDropDown.text = selectedText
hash = hashDropDown.text
hashDropDown.closeTriggered()
}
Component.onCompleted: {
hashDropDown.text = hash
for (var i = 0; i < hashListView.model.count; i++) {
if (hashListView.model.get(i).name === hashDropDown.text) {
currentIndex = i
}
}
}
}
}
DropDownType {
id: cipherDropDown
Layout.fillWidth: true
Layout.topMargin: 16
enabled: !autoNegotiateEncryprionSwitcher.checked
descriptionText: qsTr("Cipher")
headerText: qsTr("Cipher")
drawerParent: root
listView: ListViewWithRadioButtonType {
id: cipherListView
rootWidth: root.width
model: ListModel {
ListElement { name : qsTr("AES-256-GCM") }
ListElement { name : qsTr("AES-192-GCM") }
ListElement { name : qsTr("AES-128-GCM") }
ListElement { name : qsTr("AES-256-CBC") }
ListElement { name : qsTr("AES-192-CBC") }
ListElement { name : qsTr("AES-128-CBC") }
ListElement { name : qsTr("ChaCha20-Poly1305") }
ListElement { name : qsTr("ARIA-256-CBC") }
ListElement { name : qsTr("CAMELLIA-256-CBC") }
ListElement { name : qsTr("none") }
}
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
cipherDropDown.closeTriggered()
}
Component.onCompleted: {
cipherDropDown.text = cipher
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i
}
}
}
}
}
Rectangle {
id: contentRect
Layout.fillWidth: true
Layout.topMargin: 32
Layout.preferredHeight: checkboxLayout.implicitHeight
color: AmneziaStyle.color.onyxBlack
radius: 16
Connections {
target: tlsAuthCheckBox
enabled: !GC.isMobile()
function onFocusChanged() {
if (tlsAuthCheckBox.activeFocus) {
fl.ensureVisible(contentRect)
}
}
}
ColumnLayout {
id: checkboxLayout
anchors.fill: parent
CheckBoxType {
id: tlsAuthCheckBox
Layout.fillWidth: true
text: qsTr("TLS auth")
checked: tlsAuth
onCheckedChanged: {
if (checked !== tlsAuth) {
console.log("tlsAuth changed to: " + checked)
tlsAuth = checked
}
}
}
DividerType {}
CheckBoxType {
id: blockDnsCheckBox
Layout.fillWidth: true
text: qsTr("Block DNS requests outside of VPN")
checked: blockDns
onCheckedChanged: {
if (checked !== blockDns) {
blockDns = checked
}
}
}
}
}
SwitcherType {
id: additionalClientCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 32
parentFlickable: fl
checked: additionalClientCommands !== ""
text: qsTr("Additional client configuration commands")
onCheckedChanged: {
if (!checked) {
additionalClientCommands = ""
}
}
}
TextAreaType {
id: additionalClientCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
visible: additionalClientCommandsSwitcher.checked
parentFlickable: fl
textAreaText: additionalClientCommands
placeholderText: qsTr("Commands:")
textArea.onEditingFinished: {
if (additionalClientCommands !== textAreaText) {
additionalClientCommands = textAreaText
}
}
}
SwitcherType {
id: additionalServerCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 16
parentFlickable: fl
checked: additionalServerCommands !== ""
text: qsTr("Additional server configuration commands")
onCheckedChanged: {
if (!checked) {
additionalServerCommands = ""
}
}
}
TextAreaType {
id: additionalServerCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
visible: additionalServerCommandsSwitcher.checked
textAreaText: additionalServerCommands
placeholderText: qsTr("Commands:")
parentFlickable: fl
textArea.onEditingFinished: {
if (additionalServerCommands !== textAreaText) {
additionalServerCommands = textAreaText
}
}
}
BasicButtonType {
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
enabled: vpnAddressSubnetTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
parentFlickable: fl
onClicked: function() {
forceActiveFocus()
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
saveButton.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
Keys.onEnterPressed: saveButton.clicked()
Keys.onReturnPressed: saveButton.clicked()
}
}
}
}
DropDownType {
id: cipherDropDown
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: !autoNegotiateEncryprionSwitcher.checked
descriptionText: qsTr("Cipher")
headerText: qsTr("Cipher")
drawerParent: root
listView: ListViewWithRadioButtonType {
id: cipherListView
rootWidth: root.width
model: ListModel {
ListElement { name : qsTr("AES-256-GCM") }
ListElement { name : qsTr("AES-192-GCM") }
ListElement { name : qsTr("AES-128-GCM") }
ListElement { name : qsTr("AES-256-CBC") }
ListElement { name : qsTr("AES-192-CBC") }
ListElement { name : qsTr("AES-128-CBC") }
ListElement { name : qsTr("ChaCha20-Poly1305") }
ListElement { name : qsTr("ARIA-256-CBC") }
ListElement { name : qsTr("CAMELLIA-256-CBC") }
ListElement { name : qsTr("none") }
}
clickedFunction: function() {
cipherDropDown.text = selectedText
cipher = cipherDropDown.text
cipherDropDown.closeTriggered()
}
Component.onCompleted: {
cipherDropDown.text = cipher
for (var i = 0; i < cipherListView.model.count; i++) {
if (cipherListView.model.get(i).name === cipherDropDown.text) {
currentIndex = i
}
}
}
}
}
Rectangle {
id: contentRect
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.preferredHeight: checkboxLayout.implicitHeight
color: AmneziaStyle.color.onyxBlack
radius: 16
ColumnLayout {
id: checkboxLayout
anchors.fill: parent
CheckBoxType {
id: tlsAuthCheckBox
Layout.fillWidth: true
text: qsTr("TLS auth")
checked: tlsAuth
onCheckedChanged: {
if (checked !== tlsAuth) {
console.log("tlsAuth changed to: " + checked)
tlsAuth = checked
}
}
}
DividerType {}
CheckBoxType {
id: blockDnsCheckBox
Layout.fillWidth: true
text: qsTr("Block DNS requests outside of VPN")
checked: blockDns
onCheckedChanged: {
if (checked !== blockDns) {
blockDns = checked
}
}
}
}
}
SwitcherType {
id: additionalClientCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
checked: additionalClientCommands !== ""
text: qsTr("Additional client configuration commands")
onCheckedChanged: {
if (!checked) {
additionalClientCommands = ""
}
}
}
TextAreaType {
id: additionalClientCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: additionalClientCommandsSwitcher.checked
textAreaText: additionalClientCommands
placeholderText: qsTr("Commands:")
textArea.onEditingFinished: {
if (additionalClientCommands !== textAreaText) {
additionalClientCommands = textAreaText
}
}
}
SwitcherType {
id: additionalServerCommandsSwitcher
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
checked: additionalServerCommands !== ""
text: qsTr("Additional server configuration commands")
onCheckedChanged: {
if (!checked) {
additionalServerCommands = ""
}
}
}
TextAreaType {
id: additionalServerCommandsTextArea
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: additionalServerCommandsSwitcher.checked
textAreaText: additionalServerCommands
placeholderText: qsTr("Commands:")
textArea.onEditingFinished: {
if (additionalServerCommands !== textAreaText) {
additionalServerCommands = textAreaText
}
}
}
BasicButtonType {
id: saveButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: vpnAddressSubnetTextField.errorText === "" &&
portTextField.errorText === ""
text: qsTr("Save")
onClicked: function() {
forceActiveFocus()
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
var noButtonText = qsTr("Cancel")
var yesButtonFunction = function() {
if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) {
PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection"))
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
saveButton.forceActiveFocus()
}
}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
Keys.onEnterPressed: saveButton.clicked()
Keys.onReturnPressed: saveButton.clicked()
}
}
}
}
@@ -66,8 +66,6 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("VPN address subnet")
textField.text: subnetAddress
@@ -87,8 +85,6 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
enabled: delegateItem.isEnabled
headerText: qsTr("Port")
textField.text: port
textField.maximumLength: 5
-4
View File
@@ -43,8 +43,6 @@ PageType {
LabelWithButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isVisible
@@ -68,8 +66,6 @@ PageType {
visible: GC.isDesktop()
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Close application")
leftImageSource: "qrc:/images/controls/x-circle.svg"
@@ -0,0 +1,126 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
PageType {
id: root
property bool isSending: false
function getAddedDeviceName() {
try {
var obj = JSON.parse(TransferController.pendingQrCode)
if (obj && obj.name && obj.name.length > 0) {
return obj.name
}
} catch (e) {}
return qsTr("Device")
}
function getAvailableCount() {
var max = ApiAccountInfoModel.data("maxDeviceCount")
var active = ApiAccountInfoModel.data("activeDeviceCount")
if (!max || max <= 0) max = 7
if (!active || active < 0) active = 0
var remain = max - active
return remain > 0 ? remain : 0
}
ListViewType {
id: listView
anchors.fill: parent
anchors.topMargin: 20
header: ColumnLayout {
width: listView.width
BackButtonType {
backButtonFunction: function() {
if (root.isSending) {
TransferController.stopWaitForConfig()
}
PageController.closePage()
}
}
BaseHeaderType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Add a new device to the subscription?")
descriptionText: qsTr("Devices available with Amnezia Premium: (%1)").arg(getAvailableCount())
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
text: qsTr("Add Device")
enabled: !root.isSending && root.getAvailableCount() > 0 && TransferController.pendingQrCode !== ""
clickedFunc: function() {
if (TransferController.pendingQrCode !== "") {
root.isSending = true
TransferController.onTransferQrScanned(TransferController.pendingQrCode)
}
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.paleGray
borderColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Cancel")
enabled: !root.isSending
clickedFunc: function() {
PageController.closePage()
}
}
}
}
Connections {
target: TransferController
function onPostStarted() {
PageController.showNotificationMessage(qsTr("Sending configuration..."))
}
function onPostSucceeded() {
root.isSending = false
PageController.showNotificationMessage(qsTr("%1 has been added to your subscription").arg(root.getAddedDeviceName()))
PageController.closePage()
PageController.closePage()
}
function onPostFailed(message) {
root.isSending = false
PageController.showErrorMessage(message)
}
}
}
@@ -0,0 +1,140 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import QRCodeReader 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
PageType {
id: root
property bool scanCompleted: false
Item {
id: cameraArea
anchors.fill: parent
}
Loader {
id: iosQrLoader
anchors.fill: cameraArea
active: Qt.platform.os === "ios"
sourceComponent: Component {
QRCodeReader {
id: qrCodeReader
function updateCameraSize() {
qrCodeReader.setCameraSize(Qt.rect(cameraArea.x,
cameraArea.y,
cameraArea.width,
cameraArea.height))
}
onCodeReaded: function(code) {
if (!code || code.length === 0) {
return
}
var obj = null
try {
obj = JSON.parse(code)
} catch (e) {
obj = null
}
if (!obj || !obj.gw || !(obj.uuid || obj.u)) {
return
}
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
if (obj.name && obj.name.length > 0) {
normalizedObj.name = obj.name
}
var normalized = JSON.stringify(normalizedObj)
TransferController.setPendingQrCode(normalized)
qrCodeReader.stopReading()
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
}
Component.onCompleted: {
updateCameraSize()
qrCodeReader.startReading()
}
Component.onDestruction: qrCodeReader.stopReading()
}
}
}
onWidthChanged: {
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
iosQrLoader.item.updateCameraSize()
}
}
onHeightChanged: {
if (iosQrLoader.item && iosQrLoader.item.updateCameraSize) {
iosQrLoader.item.updateCameraSize()
}
}
Connections {
target: TransferController
function onScannerShouldStop() {
if (iosQrLoader.item && iosQrLoader.item.stopReading) {
iosQrLoader.item.stopReading()
}
}
}
Connections {
target: ImportController
function onTransferQrDecoded(code) {
if (!code || code.length === 0) {
return
}
var obj = null
try {
obj = JSON.parse(code)
} catch (e) {
obj = null
}
if (obj && obj.gw && (obj.uuid || obj.u)) {
var normalizedObj = { gw: obj.gw, uuid: (obj.uuid ? obj.uuid : obj.u) }
if (obj.name && obj.name.length > 0) {
normalizedObj.name = obj.name
}
code = JSON.stringify(normalizedObj)
}
root.scanCompleted = true
TransferController.setPendingQrCode(code)
Qt.callLater(function() {
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceConfirm)
})
}
function onQrDecodingFinished() {
if (Qt.platform.os === "android" && !root.scanCompleted) {
PageController.closePage()
}
}
}
Component.onCompleted: {
TransferController.setPendingQrCode("")
root.scanCompleted = false
if (Qt.platform.os === "android") {
ImportController.startDecodingQr()
}
}
}
+106 -5
View File
@@ -4,6 +4,7 @@ import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import QRCodeReader 1.0
import SortFilterProxyModel 0.2
@@ -19,6 +20,29 @@ import "../Components"
PageType {
id: root
function isAtDeviceLimit() {
var maxDeviceCount = ApiAccountInfoModel.data("maxDeviceCount")
return listView.count >= maxDeviceCount
}
function getConfigFilesCount() {
try {
var arr = ApiAccountInfoModel.getIssuedConfigsInfo()
if (!arr) return 0
var count = 0
for (var i = 0; i < arr.length; i++) {
var item = arr[i]
if (item && item["source_type"] === "country_config") {
++count
}
}
return count
} catch (e) {
return 0
}
}
ListViewType {
id: listView
@@ -46,16 +70,41 @@ PageType {
descriptionText: qsTr("Manage currently connected devices")
}
WarningType {
BasicButtonType {
id: addDeviceQrButton
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
visible: GC.isMobile()
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.paleGray
borderColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Add device by QR code")
clickedFunc: function() {
if (root.isAtDeviceLimit()) {
PageController.goToPage(PageEnum.PageSettingsApiDevicesLimit)
} else {
PageController.goToPage(PageEnum.PageSettingsApiAddDeviceScan)
}
}
}
SmallTextType {
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.fillWidth: true
textString: qsTr("You can find the identifier on the Support tab or, for older versions of the app, "
+ "by tapping '+' and then the three dots at the top of the page.")
iconPath: "qrc:/images/controls/alert-circle.svg"
text: qsTr("On the other device, tap + at the bottom → Connect to Amnezia Premium")
}
}
@@ -93,6 +142,58 @@ PageType {
DividerType {}
}
footer: ColumnLayout {
width: listView.width
LabelWithButtonType {
Layout.fillWidth: true
Layout.topMargin: 6
text: qsTr("Configuration Files: {%1}").arg(root.getConfigFilesCount())
descriptionText: qsTr("Generated configuration files also count towards the device limit")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
ApiSettingsController.updateApiCountryModel()
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
}
}
DividerType {}
WarningType {
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 8
Layout.fillWidth: true
textString: qsTr("The Support tag is available on the Support page. In older versions: tap + in the bottom bar, then More (...) in the top-right.")
iconPath: "qrc:/images/controls/alert-circle.svg"
}
}
Connections {
target: TransferController
function onPostStarted() {
PageController.showBusyIndicator(true)
}
function onPostSucceeded() {
PageController.showBusyIndicator(false)
ApiSettingsController.getAccountInfo(true)
PageController.showNotificationMessage(qsTr("New device added to subscription"))
}
function onPostFailed(message) {
PageController.showBusyIndicator(false)
PageController.showErrorMessage(message)
}
function onScannerShouldStop() {}
}
}
function deactivateExternalDevice(supportTag, countryCode) {
@@ -0,0 +1,53 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
PageType {
id: root
ListViewType {
id: listView
anchors.fill: parent
anchors.topMargin: 20
header: ColumnLayout {
width: listView.width
BackButtonType {}
BaseHeaderType {
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Your Amnezia Premium subscription already has the maximum number of devices — ") + ApiAccountInfoModel.data("connectedDevices")
descriptionText: qsTr("Remove one of the previously connected devices to add a new one")
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
text: qsTr("Show all devices")
clickedFunc: function() {
PageController.goToPage(PageEnum.PageSettingsApiDevices)
}
}
}
}
}
@@ -104,7 +104,7 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name
headerText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
descriptionText: ApiAccountInfoModel.data("serviceDescription")
actionButtonFunction: function() {
@@ -214,9 +214,6 @@ PageType {
ApiConfigsController.prepareVpnKeyExport()
PageController.showBusyIndicator(false)
// Navigate to PageShareConnection page
//PageController.goToPage(PageEnum.PageShareConnection)
}
}
@@ -416,6 +413,6 @@ PageType {
anchors.fill: parent
expandedHeight: parent.height * 0.35
serverNameText: root.processedServer.name
serverNameText: root.processedServer && root.processedServer.name ? root.processedServer.name : ""
}
}
@@ -140,7 +140,7 @@ PageType {
}
DividerType {
visible: !GC.isMobile()
visible: !GC.isMobile() && !IsMacOsNeBuild
}
SwitcherType {
+7 -11
View File
@@ -66,6 +66,13 @@ PageType {
text: qsTr("If AmneziaDNS is not used or installed")
}
}
model: 1 // fake model to force the ListView to be created without a model
delegate: ColumnLayout {
width: listView.width
spacing: 16
TextFieldWithHeaderType {
id: primaryDns
@@ -96,13 +103,6 @@ PageType {
regularExpression: InstallController.ipAddressRegExp()
}
}
}
model: 1 // fake model to force the ListView to be created without a model
spacing: 16
delegate: ColumnLayout {
width: listView.width
BasicButtonType {
id: restoreDefaultButton
@@ -139,10 +139,6 @@ PageType {
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
}
}
}
footer: ColumnLayout {
width: listView.width
BasicButtonType {
id: saveButton
+6 -2
View File
@@ -165,7 +165,11 @@ PageType {
}
}
property list<QtObject> logTypes: [
// Show service logs only if this is NOT a macOS build with
// Network-Extension (IsMacOsNeBuild is injected from C++ at run-time)
property list<QtObject> logTypes: IsMacOsNeBuild ? [
clientLogs
] : [
clientLogs,
serviceLogs
]
@@ -204,7 +208,7 @@ PageType {
readonly property string title: qsTr("Service logs")
readonly property string description: qsTr("AmneziaVPN-service logs")
readonly property bool isVisible: !GC.isMobile()
readonly property bool isVisible: !GC.isMobile() && !IsMacOsNeBuild
readonly property var openLogsHandler: function() {
SettingsController.openServiceLogsFolder()
}
@@ -18,6 +18,8 @@ PageType {
signal lastItemTabClickedSignal()
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
Connections {
target: InstallController
@@ -59,15 +61,13 @@ PageType {
target: ServersModel
function onProcessedServerIndexChanged() {
listView.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
root.isServerWithWriteAccess = ServersModel.isProcessedServerHasWriteAccess()
}
}
ListViewType {
id: listView
property bool isServerWithWriteAccess: ServersModel.isProcessedServerHasWriteAccess()
anchors.fill: parent
model: serverActions
@@ -107,7 +107,7 @@ PageType {
QtObject {
id: check
property bool isVisible: true
property bool isVisible: root.isServerWithWriteAccess
readonly property string title: qsTr("Check the server for previously installed Amnezia services")
readonly property string description: qsTr("Add them to the application if they were not displayed")
readonly property var tColor: AmneziaStyle.color.paleGray
@@ -121,7 +121,7 @@ PageType {
QtObject {
id: reboot
property bool isVisible: true
property bool isVisible: root.isServerWithWriteAccess
readonly property string title: qsTr("Reboot server")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed
@@ -181,7 +181,7 @@ PageType {
QtObject {
id: clear
property bool isVisible: true
property bool isVisible: root.isServerWithWriteAccess
readonly property string title: qsTr("Clear server from Amnezia software")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed
@@ -240,7 +240,7 @@ PageType {
QtObject {
id: switch_to_premium
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi")
property bool isVisible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium
readonly property string title: qsTr("Switch to the new Amnezia Premium subscription")
readonly property string description: ""
readonly property var tColor: AmneziaStyle.color.vibrantRed
@@ -45,7 +45,7 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Connection")
headerText: qsTr("New Connection")
actionButtonImage: isVisible ? "qrc:/images/controls/more-vertical.svg" : ""
actionButtonFunction: function() {
@@ -156,7 +156,7 @@ PageType {
Layout.leftMargin: 16
Layout.bottomMargin: 24
text: qsTr("Insert the key, add a configuration file or scan the QR-code")
text: qsTr("Enter a connection key, import a configuration file, or scan a QR code")
}
TextFieldWithHeaderType {
@@ -166,8 +166,8 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
headerText: qsTr("Insert key")
buttonText: qsTr("Insert")
headerText: qsTr("Connection key")
buttonText: qsTr("Paste")
clickedFunc: function() {
textField.text = ""
@@ -250,7 +250,7 @@ PageType {
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Site Amnezia")
text: qsTr("Amnezia Website")
rightImageSource: "qrc:/images/controls/external-link.svg"
@@ -263,18 +263,17 @@ PageType {
property list<QtObject> variants: [
amneziaVpn,
shareViaDevice,
selfHostVpn,
backupRestore,
fileOpen,
qrScan,
siteLink
qrScan
]
QtObject {
id: amneziaVpn
property string title: qsTr("VPN by Amnezia")
property string description: qsTr("Connect to classic paid and free VPN services from Amnezia")
property string title: qsTr("Choose a VPN from Amnezia")
property string description: qsTr("Paid and free VPN services from Amnezia")
property string imageSource: "qrc:/images/controls/amnezia.svg"
property bool isVisible: true
property var handler: function() {
@@ -287,11 +286,23 @@ PageType {
}
}
QtObject {
id: shareViaDevice
property string title: qsTr("Connect to Amnezia Premium")
property string description: qsTr("Via QR code from a phone with an active subscription")
property string imageSource: "qrc:/images/controls/monitor-with-phone.svg"
property bool isVisible: true
property var handler: function() {
PageController.goToPage(PageEnum.PageTransferConfigViaQR)
}
}
QtObject {
id: selfHostVpn
property string title: qsTr("Self-hosted VPN")
property string description: qsTr("Configure Amnezia VPN on your own server")
property string title: qsTr("Set up a Self-hosted VPN")
property string description: qsTr("On a private server")
property string imageSource: "qrc:/images/controls/server.svg"
property bool isVisible: true
property var handler: function() {
@@ -320,8 +331,8 @@ PageType {
QtObject {
id: fileOpen
property string title: qsTr("File with connection settings")
property string description: qsTr("")
property string title: qsTr("Use a configuration file")
property string description: qsTr("Supported formats: .conf, .vpn, .ovpn, .json")
property string imageSource: "qrc:/images/controls/folder-search-2.svg"
property bool isVisible: true
property var handler: function() {
@@ -339,8 +350,8 @@ PageType {
QtObject {
id: qrScan
property string title: qsTr("QR code")
property string description: qsTr("")
property string title: qsTr("Scan a QR code")
property string description: qsTr("To connect to a self-hosted server")
property string imageSource: "qrc:/images/controls/scan-line.svg"
property bool isVisible: SettingsController.isCameraPresent()
property var handler: function() {
@@ -161,10 +161,4 @@ PageType {
}
}
}
ShareConnectionDrawer {
id: shareConnectionDrawer
anchors.fill: parent
}
}
@@ -0,0 +1,126 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import PageEnum 1.0
import Style 1.0
import "../Controls2"
import "../Components"
import "../Controls2/TextTypes"
PageType {
id: root
objectName: "PageTransferConfigViaQR"
Rectangle {
anchors.fill: parent
color: AmneziaStyle.color.midnightBlack
z: 0
}
ColumnLayout {
z: 1
anchors.fill: parent
anchors.topMargin: 24
spacing: 12
BackButtonType {
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.alignment: Qt.AlignLeft
}
Item {
id: contentArea
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
ColumnLayout {
id: qrContent
anchors.fill: parent
spacing: 16
Item { Layout.fillHeight: true }
SmallTextType {
id: topHint
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: qsTr("Scan this QR code with a phone that has an active\nAmnezia Premium subscription")
}
Rectangle {
id: qrFrame
Layout.alignment: Qt.AlignHCenter
property real maxByHeight: qrContent.height
- topHint.implicitHeight
- bottomHint.implicitHeight
- (qrContent.spacing * 2)
property real qrSize: Math.max(180, Math.min(qrContent.width, Math.max(0, maxByHeight)))
Layout.preferredWidth: qrSize
Layout.preferredHeight: qrSize
radius: 16
color: "white"
Image {
id: qrImage
anchors.fill: parent
anchors.margins: 12
fillMode: Image.PreserveAspectFit
smooth: false
sourceSize: Qt.size(Math.round(width), Math.round(height))
source: TransferController.qrCodeUrl
visible: TransferController.qrCodeUrl !== ""
}
BusyIndicator {
anchors.centerIn: parent
running: TransferController.qrCodeUrl === ""
visible: TransferController.qrCodeUrl === ""
}
}
SmallTextType {
id: bottomHint
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: qsTr("AmneziaVPN → Amnezia Premium →\nPersonal Dashboard → Active Devices →\nAdd Device via QR Code")
}
Item { Layout.fillHeight: true }
}
}
}
Connections {
target: TransferController
function onConfigApplied() {
PageController.showNotificationMessage(qsTr("Device has been added to subscription"))
PageController.closePage()
PageController.goToPageHome()
}
function onWaitError(message) {
PageController.showErrorMessage(message)
}
}
Connections {
target: ImportController
function onImportErrorOccurred(errorCode, goToPageHome) {
PageController.showErrorMessage(errorCode)
}
}
Component.onCompleted: {
TransferController.generateNewQrCode()
TransferController.startWaitForConfig(ImportController)
}
Component.onDestruction: TransferController.stopWaitForConfig()
}
+2 -1
View File
@@ -26,7 +26,8 @@ Window {
color: AmneziaStyle.color.midnightBlack
onClosing: function() {
onClosing: function(close) {
close.accepted = false
PageController.closeWindow()
}
+5 -3
View File
@@ -38,9 +38,11 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) :
QDesktopServices::openUrl(QUrl(websiteUrl));
});
m_trayActionQuit = m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){
qApp->quit();
});
// Quit action: disconnect VPN first on macOS NE, else quit directly
m_trayActionQuit = m_menu.addAction(QIcon(":/images/tray/cancel.png"),
tr("Quit") + " " + APPLICATION_NAME,
this,
[&](){ qApp->quit(); });
m_systemTrayIcon.setContextMenu(&m_menu);
setTrayState(Vpn::ConnectionState::Disconnected);
+1 -1
View File
@@ -190,7 +190,7 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
CloseHandle(hSnapshot);
return false;
#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID)
#elif defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(MACOS_NE)
return false;
#else
QProcess process;
+16 -22
View File
@@ -22,7 +22,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"
#endif
@@ -33,7 +33,7 @@ VpnConnection::VpnConnection(std::shared_ptr<Settings> settings, QObject *parent
: QObject(parent), m_settings(settings), m_checkTimer(new QTimer(this))
{
m_checkTimer.setInterval(1000);
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged);
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
@@ -122,7 +122,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
}
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
if (state == Vpn::ConnectionState::Connected) {
m_checkTimer.start();
} else {
@@ -236,10 +236,11 @@ ErrorCode VpnConnection::lastError() const
void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &vpnConfiguration)
{
qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2")
qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is")
.arg(serverIndex)
.arg(ContainerProps::containerToString(container));
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
.arg(ContainerProps::containerToString(container))
<< m_settings->routeMode();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (!m_IpcClient) {
m_IpcClient = new IpcClient(this);
}
@@ -270,7 +271,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
appendSplitTunnelingConfig();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
if (!m_vpnProtocol) {
emit connectionStateChanged(Vpn::ConnectionState::Error);
@@ -282,7 +283,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
createAndroidConnections();
m_vpnProtocol.reset(androidVpnProtocol);
#elif defined Q_OS_IOS
#elif defined Q_OS_IOS || defined(MACOS_NE)
Proto proto = ContainerProps::defaultProtocol(container);
IosController::Instance()->connectVpn(proto, m_vpnConfiguration);
connect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
@@ -362,20 +363,20 @@ void VpnConnection::appendSplitTunnelingConfig()
}
}
Settings::RouteMode sitesRouteMode = Settings::RouteMode::VpnAllSites;
Settings::RouteMode routeMode = Settings::RouteMode::VpnAllSites;
QJsonArray sitesJsonArray;
if (m_settings->isSitesSplitTunnelingEnabled()) {
sitesRouteMode = m_settings->routeMode();
routeMode = m_settings->routeMode();
if (allowSiteBasedSplitTunneling) {
auto sites = m_settings->getVpnIps(sitesRouteMode);
auto sites = m_settings->getVpnIps(routeMode);
for (const auto &site : sites) {
sitesJsonArray.append(site);
}
if (sitesJsonArray.isEmpty()) {
sitesRouteMode = Settings::RouteMode::VpnAllSites;
} else if (sitesRouteMode == Settings::VpnOnlyForwardSites) {
routeMode = Settings::RouteMode::VpnAllSites;
} else if (routeMode == Settings::VpnOnlyForwardSites) {
// Allow traffic to Amnezia DNS
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns1).toString());
sitesJsonArray.append(m_vpnConfiguration.value(config_key::dns2).toString());
@@ -383,7 +384,7 @@ void VpnConnection::appendSplitTunnelingConfig()
}
}
m_vpnConfiguration.insert(config_key::splitTunnelType, sitesRouteMode);
m_vpnConfiguration.insert(config_key::splitTunnelType, routeMode);
m_vpnConfiguration.insert(config_key::splitTunnelSites, sitesJsonArray);
Settings::AppsRouteMode appsRouteMode = Settings::AppsRouteMode::VpnAllApps;
@@ -403,13 +404,6 @@ void VpnConnection::appendSplitTunnelingConfig()
m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode);
m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray);
qDebug() << QString("Site split tunneling is %1, route mode is %2")
.arg(m_settings->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled")
.arg(sitesRouteMode);
qDebug() << QString("App split tunneling is %1, route mode is %2")
.arg(m_settings->isAppsSplitTunnelingEnabled() ? "enabled" : "disabled")
.arg(appsRouteMode);
}
#ifdef Q_OS_ANDROID
@@ -471,7 +465,7 @@ void VpnConnection::disconnectFromVpn()
}
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
IosController::Instance()->disconnectVpn();
disconnect(&m_checkTimer, &QTimer::timeout, IosController::Instance(), &IosController::checkStatus);
#endif
+1 -1
View File
@@ -32,7 +32,7 @@ cmake --version
clang -v
# Generate XCodeProj
$QT_BIN_DIR/qt-cmake . -B $BUILD_DIR -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
$QT_BIN_DIR/qt-cmake . -B $BUILD_DIR -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR -DDEPLOY=ON
KEYCHAIN=amnezia.build.ios.keychain
KEYCHAIN_FILE=$HOME/Library/Keychains/${KEYCHAIN}-db
+122
View File
@@ -0,0 +1,122 @@
#!/bin/bash
echo "Build script for macOS Network Extension started ..."
set -o errexit -o nounset
while getopts n flag
do
case "${flag}" in
n) NOTARIZE_APP=1;;
esac
done
# Hold on to current directory
PROJECT_DIR=$(pwd)
DEPLOY_DIR=$PROJECT_DIR/deploy
mkdir -p $DEPLOY_DIR/build-macos
BUILD_DIR=$DEPLOY_DIR/build-macos
echo "Project dir: ${PROJECT_DIR}"
echo "Build dir: ${BUILD_DIR}"
APP_NAME=AmneziaVPN
APP_FILENAME=$APP_NAME.app
APP_DOMAIN=org.amneziavpn.package
PLIST_NAME=$APP_NAME.plist
OUT_APP_DIR=$BUILD_DIR/client
BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME
PREBUILT_DEPLOY_DATA_DIR=$PROJECT_DIR/deploy/data/deploy-prebuilt/macos
DEPLOY_DATA_DIR=$PROJECT_DIR/deploy/data/macos
INSTALLER_DATA_DIR=$BUILD_DIR/installer/packages/$APP_DOMAIN/data
INSTALLER_BUNDLE_DIR=$BUILD_DIR/installer/$APP_FILENAME
DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg
echo "Import certificate"
TRUST_CERT_CER=$BUILD_DIR/trust-cert.cer
SIGNING_CERT_P12=$BUILD_DIR/signing-cert.p12
echo $MAC_TRUST_CERT_BASE64 | base64 --decode > $TRUST_CERT_CER
echo $MAC_SIGNING_CERT_BASE64 | base64 --decode > $SIGNING_CERT_P12
shasum -a 256 $TRUST_CERT_CER
shasum -a 256 $SIGNING_CERT_P12
KEYCHAIN_PASS=$MAC_SIGNING_CERT_PASSWORD
# Keychain setup
KEYCHAIN=amnezia.build.macos.keychain
TEMP_PASS=tmp_pass
KEYCHAIN_FILE=$HOME/Library/Keychains/$KEYCHAIN-db
security create-keychain -p $TEMP_PASS $KEYCHAIN || true
security default-keychain -s $KEYCHAIN
security unlock-keychain -p $TEMP_PASS $KEYCHAIN
security default-keychain
security list-keychains
# Import certificates into keychain
security import $TRUST_CERT_CER -k $KEYCHAIN -P "" -T /usr/bin/codesign || true
security import $SIGNING_CERT_P12 -k $KEYCHAIN -P $MAC_SIGNING_CERT_PASSWORD -T /usr/bin/codesign || true
# Configure keychain settings
security set-key-partition-list -S apple-tool:,apple: -k $TEMP_PASS $KEYCHAIN
security find-identity -p codesigning
# Setup provisioning profiles for main app and NE
echo "Setting up provisioning profiles..."
# Copy provisioning prifiles
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles/"
echo $MAC_APP_PROVISIONING_PROFILE | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/app.mobileprovision
echo $MAC_NE_PROVISIONING_PROFILE | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/ne.mobileprovision
shasum -a 256 ~/Library/MobileDevice/Provisioning\ Profiles/app.mobileprovision
shasum -a 256 ~/Library/MobileDevice/Provisioning\ Profiles/ne.mobileprovision
profile_uuid=`grep UUID -A1 -a ~/Library/MobileDevice/Provisioning\ Profiles/app.mobileprovision | grep -io "[-A-F0-9]\{36\}"`
echo $profile_uuid
profile_ne_uuid=`grep UUID -A1 -a ~/Library/MobileDevice/Provisioning\ Profiles/ne.mobileprovision | grep -io "[-A-F0-9]\{36\}"`
echo $profile_ne_uuid
mv ~/Library/MobileDevice/Provisioning\ Profiles/app.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$profile_uuid.mobileprovision
mv ~/Library/MobileDevice/Provisioning\ Profiles/ne.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$profile_ne_uuid.mobileprovision
# setup environment
QT_MACOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
echo "QT_BIN_DIR: $QT_BIN_DIR"
# Build the Network Extension app
echo "Building MAC Network Extension App..."
mkdir -p build-macos
$QT_MACOS_BIN/qt-cmake . -B build-macos -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR -DMACOS_NE=TRUE -DCMAKE_BUILD_TYPE=Release -DDEPLOY=ON
# Build and run tests here
echo "____________________________________"
echo "............Deploying..............."
echo "____________________________________"
echo "Deploying MAC Network Extension App..."
echo "xcode build"
xcodebuild \
"OTHER_CODE_SIGN_FLAGS=--keychain '$KEYCHAIN_FILE'" \
-configuration Release \
-scheme AmneziaVPN \
-destination "platform=macOS" \
-project $PROJECT_DIR/build-macos/AmneziaVPN.xcodeproj
# Restore keychain to default
echo "Restoring default keychain..."
security default-keychain -s "/Users/runner/Library/Keychains/login.keychain-db"
echo "Build and signing process completed successfully!"
+1 -1
View File
@@ -6,6 +6,6 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION})
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(server)
endif()