mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4cf0e477c1 | |||
| 426a95c425 | |||
| 5e52f7fcb0 | |||
| c9a1b2e451 | |||
| 6087375fb0 |
@@ -18,6 +18,7 @@ jobs:
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
base: ${{ github.event.before }}
|
||||
filters: |
|
||||
recipes:
|
||||
- 'recipes/**'
|
||||
@@ -39,7 +40,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
shell: bash
|
||||
@@ -49,11 +50,9 @@ jobs:
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -99,7 +98,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Install system packages'
|
||||
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
@@ -119,7 +118,7 @@ jobs:
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_linux_x64.run
|
||||
path: deploy/build/AmneziaVPN-*-Linux.run
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
@@ -150,17 +149,15 @@ jobs:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -232,7 +229,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
@@ -245,31 +242,27 @@ jobs:
|
||||
- name: 'Upload WIX installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_windows_x64.msi
|
||||
path: deploy/build/AmneziaVPN-*-win64.msi
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload IFW installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_windows_x64.exe
|
||||
path: deploy/build/AmneziaVPN-*-win64.exe
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-iOS:
|
||||
runs-on: macos-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [26.0, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
xcode-version: [26.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -286,17 +279,15 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -353,7 +344,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.0'
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -385,7 +376,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install deps'
|
||||
run: pip install "conan==2.28.0" jsonschema jinja2
|
||||
run: pip install "conan==2.26.2" jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
@@ -403,17 +394,14 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
xcode-version: [16.2, 16.4]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -430,17 +418,15 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -516,7 +502,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
@@ -532,7 +518,7 @@ jobs:
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_macos_x64.pkg
|
||||
path: deploy/build/AmneziaVPN-*-Darwin.pkg
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
@@ -562,17 +548,15 @@ jobs:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DMACOS_NE=TRUE
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -651,7 +635,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
@@ -687,7 +671,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
@@ -712,11 +696,9 @@ jobs:
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
@@ -730,7 +712,7 @@ jobs:
|
||||
env:
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
QT_VERSION: 6.10.3
|
||||
QT_VERSION: 6.10.1
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
@@ -824,7 +806,7 @@ jobs:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
run: pip install "conan==2.26.2"
|
||||
|
||||
- name: 'Decode keystore secret to file'
|
||||
env:
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.0)
|
||||
set(AMNEZIAVPN_VERSION 4.8.15.4)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2122)
|
||||
set(APP_ANDROID_VERSION_CODE 2120)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -24,5 +24,8 @@
|
||||
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
||||
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
||||
|
||||
<string name="vpnStateEventChannelName">Уведомления о VPN</string>
|
||||
<string name="vpnStateEventChannelDescription">Краткие оповещения при подключении и отключении VPN</string>
|
||||
|
||||
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
||||
</resources>
|
||||
@@ -24,5 +24,8 @@
|
||||
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
||||
<string name="openNotificationSettings">Open notification settings</string>
|
||||
|
||||
<string name="vpnStateEventChannelName">VPN connection alerts</string>
|
||||
<string name="vpnStateEventChannelDescription">Brief alerts when VPN connects or disconnects</string>
|
||||
|
||||
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
|
||||
</resources>
|
||||
@@ -1003,6 +1003,11 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
|
||||
|
||||
@Suppress("unused")
|
||||
fun showVpnStateNotification(title: String, message: String) {
|
||||
ServiceNotification.showVpnStateEvent(applicationContext, title, message)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun requestNotificationPermission() {
|
||||
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
|
||||
@@ -26,6 +26,9 @@ private const val OLD_NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notific
|
||||
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
|
||||
const val NOTIFICATION_ID = 1337
|
||||
|
||||
const val VPN_STATE_EVENT_NOTIFICATION_ID = 1338
|
||||
private const val VPN_STATE_EVENT_CHANNEL_ID = "org.amnezia.vpn.vpn_state_events"
|
||||
|
||||
private const val GET_ACTIVITY_REQUEST_CODE = 0
|
||||
private const val CONNECT_REQUEST_CODE = 1
|
||||
private const val DISCONNECT_REQUEST_CODE = 2
|
||||
@@ -162,8 +165,42 @@ class ServiceNotification(private val context: Context) {
|
||||
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
||||
.build()
|
||||
)
|
||||
createNotificationChannel(
|
||||
Builder(VPN_STATE_EVENT_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setShowBadge(false)
|
||||
.setSound(null, null)
|
||||
.setVibrationEnabled(false)
|
||||
.setLightsEnabled(false)
|
||||
.setName(context.getString(R.string.vpnStateEventChannelName))
|
||||
.setDescription(context.getString(R.string.vpnStateEventChannelDescription))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Brief alert when VPN connects or disconnects (invoked from Qt via AmneziaActivity). */
|
||||
fun showVpnStateEvent(context: Context, title: String, message: String) {
|
||||
if (!context.isNotificationPermissionGranted()) return
|
||||
val nm = NotificationManagerCompat.from(context)
|
||||
val notification = NotificationCompat.Builder(context, VPN_STATE_EVENT_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
GET_ACTIVITY_REQUEST_CODE,
|
||||
Intent(context, AmneziaActivity::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.build()
|
||||
nm.notify(VPN_STATE_EVENT_NOTIFICATION_ID, notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ set(LIBS ${LIBS}
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macos_ne_vpn_notification.h
|
||||
${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
|
||||
@@ -42,6 +43,7 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
|
||||
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macos_ne_vpn_notification.mm
|
||||
${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
|
||||
|
||||
@@ -85,6 +85,11 @@ if(NOT ANDROID)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
|
||||
)
|
||||
else()
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
|
||||
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
@@ -178,6 +183,11 @@ if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
|
||||
)
|
||||
else()
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
|
||||
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(COMMON_FILES_H
|
||||
|
||||
@@ -460,7 +460,6 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
|
||||
if (apiV2->nameOverriddenByUser) {
|
||||
newApiV2->name = apiV2->name;
|
||||
newApiV2->displayName = apiV2->displayName;
|
||||
newApiV2->nameOverriddenByUser = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,7 @@
|
||||
#include "ui/models/ipSplitTunnelingModel.h"
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#endif
|
||||
class NotificationHandler;
|
||||
|
||||
class CoreSignalHandlers;
|
||||
class TestMultipleImports;
|
||||
@@ -144,9 +142,7 @@ private:
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
NotificationHandler* m_notificationHandler;
|
||||
#endif
|
||||
NotificationHandler* m_notificationHandler {};
|
||||
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#include "ui/utils/systemTrayNotificationHandler.h"
|
||||
#endif
|
||||
|
||||
@@ -410,22 +410,23 @@ void CoreSignalHandlers::initIosSettingsHandler()
|
||||
|
||||
void CoreSignalHandlers::initNotificationHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_coreController->m_notificationHandler = NotificationHandler::create(m_coreController);
|
||||
|
||||
connect(m_coreController->m_connectionController, &ConnectionController::connectionStateChanged, m_coreController->m_notificationHandler,
|
||||
&NotificationHandler::setConnectionState);
|
||||
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated);
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_coreController->m_notificationHandler, &NotificationHandler::raiseRequested, m_coreController->m_pageController, &PageController::raiseMainWindow);
|
||||
connect(m_coreController->m_notificationHandler, &NotificationHandler::connectRequested, m_coreController->m_connectionUiController,
|
||||
static_cast<void (ConnectionUiController::*)()>(&ConnectionUiController::openConnection));
|
||||
connect(m_coreController->m_notificationHandler, &NotificationHandler::disconnectRequested, m_coreController->m_connectionUiController,
|
||||
&ConnectionUiController::closeConnection);
|
||||
connect(m_coreController, &CoreController::translationsUpdated, m_coreController->m_notificationHandler, &NotificationHandler::onTranslationsUpdated);
|
||||
|
||||
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_coreController->m_notificationHandler);
|
||||
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||
#endif
|
||||
if (auto *trayHandler = qobject_cast<SystemTrayNotificationHandler *>(m_coreController->m_notificationHandler)) {
|
||||
connect(m_coreController, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
|
||||
@@ -226,74 +226,37 @@ void InstallController::clearCachedProfile(const QString &serverId, DockerContai
|
||||
|
||||
ErrorCode InstallController::validateAndPrepareConfig(const QString &serverId)
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
ContainerConfig containerConfig;
|
||||
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
containerConfig = cfg->containerConfig(container);
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
containerConfig = cfg->containerConfig(container);
|
||||
break;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
containerConfig = cfg->containerConfig(container);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (containerConfig.protocolConfig.hasClientConfig()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (kind != serverConfigUtils::ConfigType::SelfHostedAdmin) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
DockerContainer container = adminConfig->defaultContainer;
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfig = adminConfig->containerConfig(container);
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
SshSession sshSession;
|
||||
const QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
||||
const ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverId, clientName);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
adminConfig->updateContainerConfig(container, containerConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
auto isProtocolConfigExists = [](const ContainerConfig &cfg) {
|
||||
return cfg.protocolConfig.hasClientConfig();
|
||||
};
|
||||
|
||||
if (!isProtocolConfigExists(containerConfig)) {
|
||||
QString clientName = QString("Admin [%1]").arg(QSysInfo::prettyProductName());
|
||||
ErrorCode errorCode = processContainerForAdmin(container, containerConfig, credentials, sshSession, serverId, clientName);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, containerConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -52,7 +51,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -60,7 +58,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->description = name;
|
||||
cfg->displayName = name;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
}
|
||||
@@ -70,7 +67,6 @@ bool ServersController::renameServer(const QString &serverId, const QString &nam
|
||||
auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) return false;
|
||||
cfg->name = name;
|
||||
cfg->displayName = name;
|
||||
cfg->nameOverriddenByUser = true;
|
||||
m_serversRepository->editServer(serverId, cfg->toJson(), kind);
|
||||
return true;
|
||||
|
||||
@@ -192,36 +192,6 @@ void SettingsController::clearSettings()
|
||||
toggleAutoStart(false);
|
||||
}
|
||||
|
||||
bool SettingsController::isFileEncryptionEnabled()
|
||||
{
|
||||
return m_appSettingsRepository->isFileEncryption();
|
||||
}
|
||||
|
||||
void SettingsController::toggleFileEncryption(bool enable)
|
||||
{
|
||||
m_appSettingsRepository->setFileEncryption(enable);
|
||||
}
|
||||
|
||||
void SettingsController::setPassword(QString pwd)
|
||||
{
|
||||
m_appSettingsRepository->setPassword(pwd);
|
||||
}
|
||||
|
||||
QString SettingsController::getPassword()
|
||||
{
|
||||
return m_appSettingsRepository->getPassword();
|
||||
}
|
||||
|
||||
void SettingsController::setHint(QString hint)
|
||||
{
|
||||
m_appSettingsRepository->setHint(hint);
|
||||
}
|
||||
|
||||
QString SettingsController::getHint()
|
||||
{
|
||||
return m_appSettingsRepository->getHint();
|
||||
}
|
||||
|
||||
bool SettingsController::isAutoConnectEnabled() const
|
||||
{
|
||||
return m_appSettingsRepository->isAutoConnect();
|
||||
|
||||
@@ -44,15 +44,6 @@ public:
|
||||
|
||||
void clearSettings();
|
||||
|
||||
bool isFileEncryptionEnabled();
|
||||
void toggleFileEncryption(bool enable);
|
||||
|
||||
void setPassword(QString pwd);
|
||||
QString getPassword();
|
||||
|
||||
void setHint(QString hint);
|
||||
QString getHint();
|
||||
|
||||
bool isAutoConnectEnabled() const;
|
||||
void toggleAutoConnect(bool enable);
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ namespace
|
||||
Logger logger("UpdateController");
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_windows_x64.exe");
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-win64.exe");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN_installer.exe";
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_macos_x64.pkg");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Darwin.pkg");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.pkg";
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN_%1_linux_x64.run");
|
||||
const QLatin1String kInstallerRemoteFileNamePattern("AmneziaVPN-%1-Linux.run");
|
||||
const QString kInstallerLocalPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/AmneziaVPN.run";
|
||||
#endif
|
||||
}
|
||||
@@ -106,7 +106,7 @@ void UpdateController::fetchGatewayUrl()
|
||||
// Workaround: wait before contacting gateway to avoid rate limit triggered by other requests (news etc.)
|
||||
QTimer::singleShot(1000, this, [this, gatewayController, apiPayload]() {
|
||||
gatewayController->postAsync(QStringLiteral("%1v1/updater_endpoint"), apiPayload)
|
||||
.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
||||
.then(this, [this](QPair<ErrorCode, QByteArray> result) {
|
||||
auto [err, gatewayResponse] = result;
|
||||
if (err != ErrorCode::NoError) {
|
||||
logger.error() << errorString(err);
|
||||
@@ -184,7 +184,7 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
|
||||
logger.error() << QString("Network error occurred while fetching %1: %2 %3")
|
||||
.arg(operation, reply->errorString(), QString::number(error));
|
||||
});
|
||||
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors, [operation](const QList<QSslError> &errors) {
|
||||
QStringList errorStrings;
|
||||
for (const QSslError &err : errors) {
|
||||
@@ -196,13 +196,21 @@ void UpdateController::setupNetworkErrorHandling(QNetworkReply* reply, const QSt
|
||||
|
||||
void UpdateController::handleNetworkError(QNetworkReply* reply, const QString& operation)
|
||||
{
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
logger.error() << errorString(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
logger.error() << "Network error code:" << QString::number(static_cast<int>(reply->error()));
|
||||
logger.error() << "Error message:" << err;
|
||||
logger.error() << "HTTP status:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
logger.error() << errorString(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
|
||||
QString UpdateController::composeDownloadUrl() const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const QString fileName = QString(kInstallerRemoteFileNamePattern).arg(m_version);
|
||||
return m_baseUrl + "/" + fileName;
|
||||
#else
|
||||
@@ -212,7 +220,7 @@ QString UpdateController::composeDownloadUrl() const
|
||||
|
||||
void UpdateController::runInstaller()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_downloadUrl.isEmpty()) {
|
||||
logger.error() << "Download URL is empty";
|
||||
return;
|
||||
@@ -244,7 +252,7 @@ void UpdateController::runInstaller()
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
runWindowsInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
runMacInstaller(kInstallerLocalPath);
|
||||
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
runLinuxInstaller(kInstallerLocalPath);
|
||||
@@ -284,7 +292,7 @@ int UpdateController::runWindowsInstaller(const QString &installerPath)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS) && !defined(MACOS_NE)
|
||||
#if defined(Q_OS_MACOS)
|
||||
int UpdateController::runMacInstaller(const QString &installerPath)
|
||||
{
|
||||
// Create temporary directory for extraction
|
||||
|
||||
@@ -300,33 +300,6 @@ void SecureAppSettingsRepository::setStrictKillSwitchEnabled(bool enabled)
|
||||
setValue("Conf/strictKillSwitchEnabled", enabled);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isFileEncryption() const
|
||||
{
|
||||
return value("Sec/fileEncryption", false).toBool();
|
||||
}
|
||||
void SecureAppSettingsRepository::setFileEncryption(bool enabled)
|
||||
{
|
||||
setValue("Sec/fileEncryption", enabled);
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::getPassword() const
|
||||
{
|
||||
return value("Sec/password", "").toString();
|
||||
}
|
||||
void SecureAppSettingsRepository::setPassword(const QString &pwd)
|
||||
{
|
||||
setValue("Sec/password", pwd);
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::getHint() const
|
||||
{
|
||||
return value("Sec/hint", "").toString();
|
||||
}
|
||||
void SecureAppSettingsRepository::setHint(const QString &hint)
|
||||
{
|
||||
setValue("Sec/hint", hint);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isAutoConnect() const
|
||||
{
|
||||
return value("Conf/autoConnect", false).toBool();
|
||||
|
||||
@@ -64,13 +64,6 @@ public:
|
||||
void setKillSwitchEnabled(bool enabled);
|
||||
bool isStrictKillSwitchEnabled() const;
|
||||
void setStrictKillSwitchEnabled(bool enabled);
|
||||
|
||||
bool isFileEncryption() const;
|
||||
void setFileEncryption(bool enabled);
|
||||
QString getPassword() const;
|
||||
void setPassword(const QString &pwd);
|
||||
QString getHint() const;
|
||||
void setHint(const QString &hint);
|
||||
|
||||
bool isAutoConnect() const;
|
||||
void setAutoConnect(bool enabled);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include <QLatin1Char>
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -103,6 +104,10 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
}
|
||||
|
||||
qDebug() << QString::fromUtf8(responseBody);
|
||||
qDebug() << replyError;
|
||||
qDebug() << httpStatusCode;
|
||||
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
@@ -228,3 +233,18 @@ QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||
|
||||
return vpnKeyText;
|
||||
}
|
||||
|
||||
QString apiUtils::countryCodeBaseForFlag(const QString &fullCountryCode)
|
||||
{
|
||||
const QString trimmed = fullCountryCode.trimmed();
|
||||
if (trimmed.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
const int dashIdx = trimmed.indexOf(QLatin1Char('-'));
|
||||
const QString base = dashIdx < 0 ? trimmed : trimmed.left(dashIdx);
|
||||
const QString normalized = base.trimmed();
|
||||
if (normalized.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return normalized.toUpper();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ namespace apiUtils
|
||||
|
||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
||||
|
||||
// ISO2-style segment for flagKit assets (e.g. US-WEST -> US). Do not use in API request bodies.
|
||||
QString countryCodeBaseForFlag(const QString &fullCountryCode);
|
||||
}
|
||||
|
||||
#endif // APIUTILS_H
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 5V3C2.5 2.33696 2.76339 1.70107 3.23223 1.23223C3.70107 0.763392 4.33696 0.5 5 0.5C5.66304 0.5 6.29893 0.763392 6.76777 1.23223C7.23661 1.70107 7.5 2.33696 7.5 3V5M1.5 5H8.5C9.05229 5 9.5 5.44772 9.5 6V9.5C9.5 10.0523 9.05229 10.5 8.5 10.5H1.5C0.947715 10.5 0.5 10.0523 0.5 9.5V6C0.5 5.44772 0.947715 5 1.5 5Z" stroke="#EB5757" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 494 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 5.00252V3.00252C2.49938 2.38254 2.72914 1.78445 3.14469 1.32435C3.56023 0.864252 4.13191 0.574969 4.74875 0.512663C5.36559 0.450356 5.98357 0.61947 6.48274 0.987175C6.9819 1.35488 7.32663 1.89494 7.45 2.50252M1.5 5.00252H8.5C9.05229 5.00252 9.5 5.45023 9.5 6.00252V9.50252C9.5 10.0548 9.05229 10.5025 8.5 10.5025H1.5C0.947715 10.5025 0.5 10.0548 0.5 9.50252V6.00252C0.5 5.45023 0.947715 5.00252 1.5 5.00252Z" stroke="#5CAEE7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 592 B |
@@ -36,8 +36,6 @@
|
||||
<file>controls/home.svg</file>
|
||||
<file>controls/infinity.svg</file>
|
||||
<file>controls/info.svg</file>
|
||||
<file>controls/lock-locked.svg</file>
|
||||
<file>controls/lock-unlocked.svg</file>
|
||||
<file>controls/mail.svg</file>
|
||||
<file>controls/map-pin.svg</file>
|
||||
<file>controls/more-vertical.svg</file>
|
||||
|
||||
+2
-6
@@ -1,12 +1,13 @@
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <libssh/libssh.h>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/utils/osSignalHandler.h"
|
||||
#include "core/utils/migrations.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
@@ -46,11 +47,6 @@ int main(int argc, char *argv[])
|
||||
AmneziaApplication app(argc, argv);
|
||||
OsSignalHandler::setup();
|
||||
|
||||
ssh_init();
|
||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, []() {
|
||||
ssh_finalize();
|
||||
});
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
if (isAnotherInstanceRunning()) {
|
||||
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
|
||||
|
||||
@@ -307,6 +307,16 @@ void AndroidController::requestNotificationPermission()
|
||||
callActivityMethod("requestNotificationPermission", "()V");
|
||||
}
|
||||
|
||||
void AndroidController::showVpnStateNotification(const QString &title, const QString &message)
|
||||
{
|
||||
if (!isNotificationPermissionGranted()) {
|
||||
return;
|
||||
}
|
||||
callActivityMethod("showVpnStateNotification", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QJniObject::fromString(title).object<jstring>(),
|
||||
QJniObject::fromString(message).object<jstring>());
|
||||
}
|
||||
|
||||
bool AndroidController::requestAuthentication()
|
||||
{
|
||||
QEventLoop wait;
|
||||
|
||||
@@ -53,6 +53,7 @@ public:
|
||||
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
||||
bool isNotificationPermissionGranted();
|
||||
void requestNotificationPermission();
|
||||
void showVpnStateNotification(const QString &title, const QString &message);
|
||||
bool requestAuthentication();
|
||||
void sendTouch(float x, float y);
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "android_notificationhandler.h"
|
||||
|
||||
#include "android_controller.h"
|
||||
|
||||
AndroidNotificationHandler::AndroidNotificationHandler(QObject *parent)
|
||||
: NotificationHandler(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void AndroidNotificationHandler::notify(Message type, const QString &title, const QString &message, int timerMsec)
|
||||
{
|
||||
Q_UNUSED(type);
|
||||
Q_UNUSED(timerMsec);
|
||||
// Permission is checked on the Kotlin side as well; avoid JNI if already denied.
|
||||
if (!AndroidController::instance()->isNotificationPermissionGranted()) {
|
||||
return;
|
||||
}
|
||||
AndroidController::instance()->showVpnStateNotification(title, message);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef ANDROID_NOTIFICATIONHANDLER_H
|
||||
#define ANDROID_NOTIFICATIONHANDLER_H
|
||||
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
|
||||
class AndroidNotificationHandler final : public NotificationHandler {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AndroidNotificationHandler(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void notify(Message type, const QString &title, const QString &message, int timerMsec) override;
|
||||
};
|
||||
|
||||
#endif // ANDROID_NOTIFICATIONHANDLER_H
|
||||
@@ -61,6 +61,7 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
|
||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||
const QString& message, int timerMsec) {
|
||||
Q_UNUSED(type);
|
||||
Q_UNUSED(timerMsec);
|
||||
|
||||
if (!m_delegate) {
|
||||
return;
|
||||
@@ -71,11 +72,13 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
||||
content.body = message.toNSString();
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
int timerSec = timerMsec / 1000;
|
||||
NSTimeInterval delay = 0.1;
|
||||
UNTimeIntervalNotificationTrigger* trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
|
||||
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||
NSString* requestId = [NSString stringWithFormat:@"amneziavpn.vpnstate.%lld",
|
||||
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:requestId
|
||||
content:content
|
||||
trigger:trigger];
|
||||
|
||||
@@ -143,6 +146,7 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
|
||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||
const QString& message, int timerMsec) {
|
||||
Q_UNUSED(type);
|
||||
Q_UNUSED(timerMsec);
|
||||
|
||||
if (!m_delegate) {
|
||||
return;
|
||||
@@ -153,11 +157,13 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
||||
content.body = message.toNSString();
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
int timerSec = timerMsec / 1000;
|
||||
NSTimeInterval delay = 0.1;
|
||||
UNTimeIntervalNotificationTrigger* trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
|
||||
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||
NSString* requestId = [NSString stringWithFormat:@"amneziavpn.vpnstate.%lld",
|
||||
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:requestId
|
||||
content:content
|
||||
trigger:trigger];
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef MACOS_NE_VPN_NOTIFICATION_H
|
||||
#define MACOS_NE_VPN_NOTIFICATION_H
|
||||
|
||||
class QString;
|
||||
|
||||
void macosNePostVpnStateNotification(const QString &title, const QString &message);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "macos_ne_vpn_notification.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QString>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@interface MacosNeVpnNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
|
||||
@end
|
||||
|
||||
@implementation MacosNeVpnNotificationDelegate
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||
willPresentNotification:(UNNotification *)notification
|
||||
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
|
||||
{
|
||||
Q_UNUSED(center)
|
||||
Q_UNUSED(notification)
|
||||
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
||||
withCompletionHandler:(void (^)(void))completionHandler
|
||||
{
|
||||
Q_UNUSED(center)
|
||||
Q_UNUSED(response)
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
MacosNeVpnNotificationDelegate *delegateInstance()
|
||||
{
|
||||
static MacosNeVpnNotificationDelegate *d;
|
||||
static dispatch_once_t once;
|
||||
dispatch_once(&once, ^{
|
||||
d = [[MacosNeVpnNotificationDelegate alloc] init];
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
void ensureNotificationCenterSetup()
|
||||
{
|
||||
static dispatch_once_t once;
|
||||
dispatch_once(&once, ^{
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
||||
UNAuthorizationOptionBadge)
|
||||
completionHandler:^(BOOL granted, NSError *_Nullable error) {
|
||||
Q_UNUSED(granted);
|
||||
if (!error) {
|
||||
center.delegate = delegateInstance();
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void macosNePostVpnStateNotification(const QString &title, const QString &message)
|
||||
{
|
||||
ensureNotificationCenterSetup();
|
||||
|
||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = title.toNSString();
|
||||
content.body = message.toNSString();
|
||||
content.sound = nil;
|
||||
|
||||
NSTimeInterval delay = 0.1;
|
||||
UNTimeIntervalNotificationTrigger *trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
|
||||
|
||||
NSString *identifier =
|
||||
[NSString stringWithFormat:@"amneziavpn.vpnstate.%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
|
||||
|
||||
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
|
||||
content:content
|
||||
trigger:trigger];
|
||||
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError *_Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"macosNePostVpnStateNotification failed: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -45,7 +45,7 @@ private:
|
||||
QStringList encryptedKeys; // encode only key listed here
|
||||
// only this fields need for backup
|
||||
QStringList m_fieldsToBackup = {
|
||||
"Conf/", "Servers/", "Sec/",
|
||||
"Conf/", "Servers/",
|
||||
};
|
||||
|
||||
mutable QByteArray m_key;
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
@@ -117,8 +121,6 @@ bool SubscriptionUiController::exportNativeConfig(const QString &serverId, const
|
||||
}
|
||||
|
||||
const bool saved = SystemController::saveFile(fileName, nativeConfig);
|
||||
if (m_settingsController->isFileEncryptionEnabled())
|
||||
SystemController::encryptFile(fileName, m_settingsController->getPassword(), m_settingsController->getHint());
|
||||
getAccountInfo(serverId, true);
|
||||
return saved;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,6 @@ namespace PageLoader
|
||||
PageSettingsSplitTunneling,
|
||||
PageSettingsAppSplitTunneling,
|
||||
PageSettingsKillSwitch,
|
||||
PageSettingsAppEncryption,
|
||||
PageSettingsAppPassword,
|
||||
PageSettingsAppPasswordConfirm,
|
||||
PageSettingsApiServerInfo,
|
||||
PageSettingsApiAvailableCountries,
|
||||
PageSettingsApiSupport,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "serversUiController.h"
|
||||
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
@@ -215,7 +216,11 @@ QString ServersUiController::getDefaultServerImagePathCollapsed() const
|
||||
if (!description.isApiV2 || description.apiServerCountryCode.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(description.apiServerCountryCode.toUpper());
|
||||
const QString imageCode = apiUtils::countryCodeBaseForFlag(description.apiServerCountryCode.toUpper());
|
||||
if (imageCode.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(imageCode);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
|
||||
@@ -139,8 +139,6 @@ void SettingsUiController::backupAppConfig(const QString &fileName)
|
||||
if (!SystemController::saveFile(fileName, data)) {
|
||||
qInfo() << "SettingsUiController::backupAppConfig: save or share was cancelled or failed";
|
||||
}
|
||||
if (isFileEncryptionEnabled())
|
||||
SystemController::encryptFile(fileName, getPassword(), getHint());
|
||||
}
|
||||
|
||||
void SettingsUiController::restoreAppConfig(const QString &fileName)
|
||||
@@ -188,57 +186,6 @@ void SettingsUiController::clearSettings()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsUiController::isFileEncryptionEnabled()
|
||||
{
|
||||
return m_settingsController->isFileEncryptionEnabled();
|
||||
}
|
||||
|
||||
void SettingsUiController::toggleFileEncryption(bool enable)
|
||||
{
|
||||
m_settingsController->toggleFileEncryption(enable);
|
||||
emit fileEncryptionStateChanged();
|
||||
}
|
||||
|
||||
void SettingsUiController::setPassword(QString pwd)
|
||||
{
|
||||
m_settingsController->setPassword(pwd);
|
||||
}
|
||||
|
||||
QString SettingsUiController::getPassword()
|
||||
{
|
||||
return m_settingsController->getPassword();
|
||||
}
|
||||
|
||||
void SettingsUiController::setHint(QString hint)
|
||||
{
|
||||
m_settingsController->setHint(hint);
|
||||
}
|
||||
|
||||
QString SettingsUiController::getHint()
|
||||
{
|
||||
return m_settingsController->getHint();
|
||||
}
|
||||
|
||||
void SettingsUiController::setTempPassword(QString pwd)
|
||||
{
|
||||
tempPassword = pwd;
|
||||
}
|
||||
|
||||
QString SettingsUiController::getTempPassword()
|
||||
{
|
||||
return tempPassword;
|
||||
}
|
||||
|
||||
void SettingsUiController::setTempHint(QString hint)
|
||||
{
|
||||
tempHint = hint;
|
||||
}
|
||||
|
||||
QString SettingsUiController::getTempHint()
|
||||
{
|
||||
return tempHint;
|
||||
}
|
||||
|
||||
bool SettingsUiController::isAutoConnectEnabled()
|
||||
{
|
||||
return m_settingsController->isAutoConnectEnabled();
|
||||
|
||||
@@ -61,14 +61,6 @@ public slots:
|
||||
|
||||
void clearSettings();
|
||||
|
||||
bool isFileEncryptionEnabled();
|
||||
void toggleFileEncryption(bool enable);
|
||||
|
||||
void setPassword(QString pwd);
|
||||
QString getPassword();
|
||||
void setHint(QString hint);
|
||||
QString getHint();
|
||||
|
||||
bool isAutoConnectEnabled();
|
||||
void toggleAutoConnect(bool enable);
|
||||
|
||||
@@ -81,11 +73,6 @@ public slots:
|
||||
bool isNewsNotificationsEnabled();
|
||||
void toggleNewsNotificationsEnabled(bool enable);
|
||||
|
||||
void setTempPassword(QString pwd);
|
||||
QString getTempPassword();
|
||||
void setTempHint(QString hint);
|
||||
QString getTempHint();
|
||||
|
||||
bool isScreenshotsEnabled();
|
||||
void toggleScreenshotsEnabled(bool enable);
|
||||
|
||||
@@ -150,13 +137,7 @@ signals:
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void startMinimizedChanged();
|
||||
|
||||
void fileEncryptionStateChanged();
|
||||
void changingPassword();
|
||||
|
||||
private:
|
||||
QString tempPassword;
|
||||
QString tempHint;
|
||||
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
|
||||
@@ -11,21 +11,6 @@
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int SALT_LEN = 16;
|
||||
constexpr int IV_LEN = 12;
|
||||
constexpr int KEY_LEN = 32;
|
||||
constexpr int TAG_LEN = 16;
|
||||
constexpr int PBKDF2_ITER = 100000;
|
||||
|
||||
const QByteArray magicString { "EncData" };
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
@@ -119,313 +104,6 @@ bool SystemController::readFile(const QString &fileName, QString &data)
|
||||
return true;
|
||||
}
|
||||
|
||||
static QString opensslErrString()
|
||||
{
|
||||
unsigned long e = ERR_get_error();
|
||||
if (!e)
|
||||
return QStringLiteral("Unknown OpenSSL error");
|
||||
char buf[256];
|
||||
ERR_error_string_n(e, buf, sizeof(buf));
|
||||
return QString::fromUtf8(buf);
|
||||
}
|
||||
|
||||
static bool deriveKey(const QByteArray &password, const QByteArray &salt, QByteArray &outKey)
|
||||
{
|
||||
outKey.resize(KEY_LEN);
|
||||
const unsigned char *pw = reinterpret_cast<const unsigned char *>(password.constData());
|
||||
const unsigned char *s = reinterpret_cast<const unsigned char *>(salt.constData());
|
||||
int ok = PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(pw), password.size(), s, salt.size(), PBKDF2_ITER,
|
||||
EVP_sha256(), KEY_LEN, reinterpret_cast<unsigned char *>(outKey.data()));
|
||||
if (!ok) {
|
||||
qDebug() << opensslErrString();
|
||||
}
|
||||
return ok == 1;
|
||||
}
|
||||
|
||||
static bool aesCrypt(const QByteArray &in, const QByteArray &key, const QByteArray &iv, QByteArray &out,
|
||||
QByteArray &tag, bool encrypt)
|
||||
{
|
||||
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX *)> ctx { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
|
||||
|
||||
if (!ctx) {
|
||||
qDebug() << "EVP_CIPHER_CTX_new failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
const EVP_CIPHER *cipher = EVP_aes_256_gcm();
|
||||
|
||||
if (1 != EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, encrypt ? 1 : 0)) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (1 != EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
|
||||
reinterpret_cast<const unsigned char *>(iv.constData()), -1)) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
out.clear();
|
||||
out.resize(in.size());
|
||||
|
||||
int outlen = 0;
|
||||
|
||||
if (1 != EVP_CipherUpdate(ctx.get(), reinterpret_cast<unsigned char *>(out.data()), &outlen,
|
||||
reinterpret_cast<const unsigned char *>(in.constData()), in.size())) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
int tmplen = 0;
|
||||
|
||||
if (encrypt) {
|
||||
if (1 != EVP_CipherFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(out.data()) + outlen, &tmplen)) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
out.resize(outlen + tmplen);
|
||||
|
||||
tag.resize(TAG_LEN);
|
||||
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, TAG_LEN, tag.data())) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.constData()))) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (1 != EVP_CipherFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(out.data()) + outlen, &tmplen)) {
|
||||
qDebug() << "Authentication failed: " << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
out.resize(outlen + tmplen);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemController::encryptFile(const QString &filePath, const QString &password, const QString &hint)
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Cannot open file for read: " << f.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray content = f.readAll();
|
||||
f.close();
|
||||
|
||||
if (content.startsWith(magicString)) {
|
||||
qDebug() << "File already encrypted";
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray salt(SALT_LEN, 0);
|
||||
QByteArray iv(IV_LEN, 0);
|
||||
QByteArray key;
|
||||
QByteArray cipher;
|
||||
QByteArray tag;
|
||||
|
||||
if (1 != RAND_bytes(reinterpret_cast<unsigned char *>(salt.data()), SALT_LEN)
|
||||
|| 1 != RAND_bytes(reinterpret_cast<unsigned char *>(iv.data()), IV_LEN)) {
|
||||
qDebug() << opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!deriveKey(password.toUtf8(), salt, key))
|
||||
return false;
|
||||
|
||||
if (!aesCrypt(content, key, iv, cipher, tag, true))
|
||||
return false;
|
||||
|
||||
QByteArray out;
|
||||
QByteArray hintBytes = hint.toUtf8();
|
||||
quint32 hintLen = static_cast<quint32>(hintBytes.size());
|
||||
|
||||
out += magicString;
|
||||
out.append(reinterpret_cast<const char *>(&hintLen), sizeof(hintLen));
|
||||
out += hintBytes;
|
||||
out += salt;
|
||||
out += iv;
|
||||
out += tag;
|
||||
out += cipher;
|
||||
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
qDebug() << "Cannot open file for write: " << f.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f.write(out) != out.size()) {
|
||||
qDebug() << "Write failed";
|
||||
f.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SystemController::getDecryptedData(const QString &filePath, const QString &password)
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Cannot open file: " << f.errorString();
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray content = f.readAll();
|
||||
f.close();
|
||||
|
||||
if (!content.startsWith(magicString)) {
|
||||
qDebug() << "Invalid file format (magic missing)";
|
||||
return {};
|
||||
}
|
||||
|
||||
int pos = magicString.size();
|
||||
|
||||
if (content.size() < pos + static_cast<int>(sizeof(quint32))) {
|
||||
qDebug() << "Corrupted file (no hint length)";
|
||||
return {};
|
||||
}
|
||||
|
||||
quint32 hintLen = 0;
|
||||
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
|
||||
pos += sizeof(quint32);
|
||||
|
||||
if (content.size() < pos + static_cast<int>(hintLen) + SALT_LEN + IV_LEN + TAG_LEN) {
|
||||
qDebug() << "Corrupted file (invalid sizes)";
|
||||
return {};
|
||||
}
|
||||
|
||||
pos += hintLen;
|
||||
|
||||
QByteArray salt = content.mid(pos, SALT_LEN);
|
||||
pos += SALT_LEN;
|
||||
|
||||
QByteArray iv = content.mid(pos, IV_LEN);
|
||||
pos += IV_LEN;
|
||||
|
||||
QByteArray tag = content.mid(pos, TAG_LEN);
|
||||
pos += TAG_LEN;
|
||||
|
||||
QByteArray cipher = content.mid(pos);
|
||||
|
||||
QByteArray key;
|
||||
if (!deriveKey(password.toUtf8(), salt, key)) {
|
||||
qDebug() << "Key derivation failed";
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray plain;
|
||||
if (!aesCrypt(cipher, key, iv, plain, tag, false)) {
|
||||
qDebug() << "Decryption failed (wrong password or corrupted data)";
|
||||
return {};
|
||||
}
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
bool SystemController::isFileEncrypted(const QString &filePath)
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << "Cannot open file for read: %1", f.errorString();
|
||||
return false;
|
||||
}
|
||||
QByteArray data = f.readAll();
|
||||
f.close();
|
||||
|
||||
if (!data.startsWith(magicString)) {
|
||||
qDebug() << "File is not recognized as encrypted (magic missing)";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemController::isPasswordValid(const QString &filePath, const QString &password)
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
qDebug() << f.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray content = f.readAll();
|
||||
f.close();
|
||||
|
||||
if (!content.startsWith(magicString))
|
||||
return false;
|
||||
|
||||
int pos = magicString.size();
|
||||
|
||||
quint32 hintLen = 0;
|
||||
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
|
||||
pos += sizeof(quint32);
|
||||
pos += hintLen;
|
||||
|
||||
QByteArray salt = content.mid(pos, SALT_LEN);
|
||||
pos += SALT_LEN;
|
||||
QByteArray iv = content.mid(pos, IV_LEN);
|
||||
pos += IV_LEN;
|
||||
QByteArray tag = content.mid(pos, TAG_LEN);
|
||||
pos += TAG_LEN;
|
||||
QByteArray cipher = content.mid(pos);
|
||||
|
||||
QByteArray key;
|
||||
if (!deriveKey(password.toUtf8(), salt, key))
|
||||
return false;
|
||||
|
||||
QByteArray plain;
|
||||
bool ok = aesCrypt(cipher, key, iv, plain, tag, false);
|
||||
|
||||
if (!ok)
|
||||
qDebug() << "Wrong password";
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
QString SystemController::readHint(const QString &filePath)
|
||||
{
|
||||
if (filePath.isEmpty())
|
||||
return "";
|
||||
|
||||
QByteArray data;
|
||||
readFile(filePath, data);
|
||||
|
||||
if (!data.startsWith(magicString)) {
|
||||
qDebug() << "Not an encrypted file";
|
||||
return {};
|
||||
}
|
||||
|
||||
int pos = magicString.size();
|
||||
|
||||
if (data.size() < pos + static_cast<int>(sizeof(quint32))) {
|
||||
qDebug() << "Corrupted file (no hint length)";
|
||||
return {};
|
||||
}
|
||||
|
||||
quint32 hintLen = 0;
|
||||
memcpy(&hintLen, data.constData() + pos, sizeof(quint32));
|
||||
pos += sizeof(quint32);
|
||||
|
||||
if (data.size() < pos + static_cast<int>(hintLen)) {
|
||||
qDebug() << "Corrupted file (hint truncated)";
|
||||
return {};
|
||||
}
|
||||
|
||||
return QString::fromUtf8(data.constData() + pos, hintLen);
|
||||
}
|
||||
|
||||
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
|
||||
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
|
||||
{
|
||||
|
||||
@@ -15,23 +15,10 @@ public:
|
||||
static bool readFile(const QString &fileName, QByteArray &data);
|
||||
static bool readFile(const QString &fileName, QString &data);
|
||||
|
||||
static bool encryptFile(const QString &filePath, const QString &password, const QString &hint);
|
||||
|
||||
Q_INVOKABLE bool QEncryptFile(const QString &filePath, const QString &password, const QString &hint)
|
||||
{
|
||||
return encryptFile(filePath, password, hint);
|
||||
}
|
||||
|
||||
public slots:
|
||||
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
|
||||
const bool isSaveMode = false, const QString &defaultSuffix = "");
|
||||
|
||||
QByteArray getDecryptedData(const QString &filePath, const QString &password);
|
||||
|
||||
bool isFileEncrypted(const QString &filePath);
|
||||
bool isPasswordValid(const QString &filePath, const QString &password);
|
||||
QString readHint(const QString &filePath);
|
||||
|
||||
void setQmlRoot(QObject *qmlRoot);
|
||||
|
||||
bool isAuthenticated();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace
|
||||
@@ -41,7 +42,7 @@ QVariant ApiCountryModel::data(const QModelIndex &index, int role) const
|
||||
return countryInfo.countryName;
|
||||
}
|
||||
case CountryImageCodeRole: {
|
||||
return countryInfo.countryCode.toUpper();
|
||||
return apiUtils::countryCodeBaseForFlag(countryInfo.countryCode);
|
||||
}
|
||||
case IsIssuedRole: {
|
||||
return isIssued;
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool linkEnabled: false
|
||||
|
||||
property string textColor: AmneziaStyle.color.paleGray
|
||||
|
||||
property string textString
|
||||
property int textFormat: Text.PlainText
|
||||
|
||||
property string iconPath
|
||||
property real iconWidth: 16
|
||||
property real iconHeight: 16
|
||||
|
||||
color: AmneziaStyle.color.onyxBlack
|
||||
radius: 32
|
||||
implicitHeight: iconHeight + 8
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
RowLayout {
|
||||
id: content
|
||||
anchors.centerIn: parent
|
||||
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
width: root.iconWidth
|
||||
height: root.iconHeight
|
||||
|
||||
source: root.iconPath
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
id: supportingText
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 8
|
||||
|
||||
text: root.textString
|
||||
textFormat: Text.RichText
|
||||
color: root.textColor
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
visible: root.linkEnabled
|
||||
|
||||
hoverEnabled: false
|
||||
|
||||
implicitHeight: root.iconHeight
|
||||
implicitWidth: linkText.width/2 + 8
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
|
||||
CaptionTextType {
|
||||
id: linkText
|
||||
|
||||
leftPadding: 4
|
||||
|
||||
width: linkText.text.length * linkText.font.pixelSize
|
||||
|
||||
text: qsTr("Learn more")
|
||||
textFormat: Text.RichText
|
||||
color: AmneziaStyle.color.goldenApricot
|
||||
}
|
||||
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally("https://storage.googleapis.com/amnezia/docs?m-path=/documentation/instructions/encryption")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
import "../Config"
|
||||
|
||||
DrawerType2 {
|
||||
id: root
|
||||
objectName: "passwordDrawer"
|
||||
|
||||
property bool fromOutside: true
|
||||
property string fileName
|
||||
property var securedFunc
|
||||
|
||||
signal restoreSecuredBackup
|
||||
signal importSecuredFile
|
||||
|
||||
expandedStateContent: ColumnLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
function onRestoreSecuredBackup() {
|
||||
root.securedFunc = function() {
|
||||
SettingsController.restoreAppConfigFromData(SystemController.getDecryptedData(fileName, passwordField.textField.text))
|
||||
}
|
||||
passwordDrawer.openTriggered()
|
||||
}
|
||||
|
||||
function onImportSecuredFile() {
|
||||
root.securedFunc = function() {
|
||||
if (ImportController.extractConfigFromData(SystemController.getDecryptedData(fileName, passwordField.textField.text))) {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
|
||||
}
|
||||
}
|
||||
passwordDrawer.openTriggered()
|
||||
}
|
||||
}
|
||||
|
||||
Header2TextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 8
|
||||
|
||||
text: qsTr("Password required")
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: passwordField
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
function onCloseTriggered() {
|
||||
passwordField.textField.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
property bool hideContent: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
headerText: qsTr("Password")
|
||||
textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal
|
||||
textField.text: textField.text
|
||||
|
||||
rightButtonClickedOnEnter: true
|
||||
|
||||
clickedFunc: function () {
|
||||
hideContent = !hideContent
|
||||
buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
|
||||
}
|
||||
|
||||
textField.onFocusChanged: {
|
||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
|
||||
textField.onTextChanged: {
|
||||
buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
|
||||
}
|
||||
}
|
||||
|
||||
LabelTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
text: qsTr("Hint: ") + (fromOutside ? SystemController.readHint(fileName) : SettingsController.getHint())
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: doneButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Done")
|
||||
|
||||
clickedFunc: function() {
|
||||
if (fromOutside) {
|
||||
if (!SystemController.isPasswordValid(fileName, passwordField.textField.text)) {
|
||||
passwordField.errorText = qsTr("Invalid password")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (passwordField.textField.text !== SettingsController.getPassword()) {
|
||||
passwordField.errorText = qsTr("Invalid password")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (root.securedFunc && typeof root.securedFunc === "function") {
|
||||
root.securedFunc()
|
||||
}
|
||||
|
||||
root.closeTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,10 +199,11 @@ Item {
|
||||
leftImageSource: root.buttonImageSource
|
||||
|
||||
anchors.top: content.top
|
||||
anchors.bottom: content.bottom
|
||||
anchors.right: content.right
|
||||
|
||||
height: root.errorText !== "" ? content.implicitHeight - errorField.height - 5: content.implicitHeight
|
||||
width: root.errorText !== "" ? content.implicitHeight - errorField.height - 5: content.implicitHeight
|
||||
height: content.implicitHeight
|
||||
width: content.implicitHeight
|
||||
squareLeftSide: true
|
||||
|
||||
clickedFunc: function() {
|
||||
|
||||
@@ -60,16 +60,6 @@ PageType {
|
||||
headerText: qsTr("Configuration Files")
|
||||
descriptionText: qsTr("For router setup or the AmneziaWG app")
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: indicator
|
||||
|
||||
visible: SettingsController.isFileEncryptionEnabled()
|
||||
linkEnabled: true
|
||||
|
||||
textString: qsTr("Encryption enabled.")
|
||||
iconPath: "qrc:/images/controls/lock-locked.svg"
|
||||
}
|
||||
}
|
||||
|
||||
delegate: ColumnLayout {
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isChangingPassword: false
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onFileEncryptionStateChanged() {
|
||||
PageController.showBusyIndicator(true)
|
||||
PageController.closePage()
|
||||
SettingsController.isFileEncryptionEnabled() ? PageController.goToPage(PageEnum.PageSettingsAppEncryption) : PageController.goToPage(PageEnum.PageSettingsAppPassword)
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showNotificationMessage(SettingsController.isFileEncryptionEnabled() ? qsTr("Encryption enabled") : qsTr("Encryption disabled"))
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
|
||||
backButtonFunction: function() {
|
||||
PageController.closePage()
|
||||
if (root.isChangingPassword) {
|
||||
root.isChangingPassword = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Password & Encryption")
|
||||
descriptionText: qsTr("Password protection for backups and configuration files.\nRequired to restore or import encrypted files.")
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.leftMargin: 8
|
||||
Layout.bottomMargin: 32
|
||||
implicitHeight: 16
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
disabledColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.goldenApricot
|
||||
|
||||
text: qsTr("Learn more")
|
||||
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally("https://storage.googleapis.com/amnezia/docs?m-path=/documentation/instructions/encryption")
|
||||
}
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: indicator
|
||||
|
||||
textString: qsTr("Password set. Encryption enabled")
|
||||
iconPath: "qrc:/images/controls/lock-locked.svg"
|
||||
}
|
||||
|
||||
|
||||
BasicButtonType {
|
||||
id: disableEncryptionButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Disable encryption")
|
||||
|
||||
clickedFunc: function() {
|
||||
passwordDrawer.securedFunc = function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
SettingsController.toggleFileEncryption(false)
|
||||
SettingsController.setPassword("")
|
||||
SettingsController.setHint("")
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
passwordDrawer.openTriggered()
|
||||
}
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: changePasswordButton
|
||||
|
||||
hoveredColor: AmneziaStyle.color.slateGray
|
||||
defaultColor: AmneziaStyle.color.midnightBlack
|
||||
textColor: AmneziaStyle.color.paleGray
|
||||
borderWidth: 1
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Change password")
|
||||
|
||||
clickedFunc: function() {
|
||||
passwordDrawer.securedFunc = function() {
|
||||
root.isChangingPassword = true
|
||||
|
||||
PageController.showBusyIndicator(true)
|
||||
PageController.closePage()
|
||||
PageController.goToPage(PageEnum.PageSettingsAppPassword)
|
||||
PageController.showBusyIndicator(false)
|
||||
|
||||
SettingsController.changingPassword()
|
||||
}
|
||||
passwordDrawer.openTriggered()
|
||||
}
|
||||
}
|
||||
|
||||
PasswordDrawer {
|
||||
id: passwordDrawer
|
||||
|
||||
fromOutside: false
|
||||
|
||||
parent: root
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.45
|
||||
}
|
||||
}
|
||||
|
||||
spacing: 16
|
||||
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
CaptionTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
textFormat: Text.RichText
|
||||
|
||||
text: qsTr("If the password is forgotten, it can be recovered. To reset the password, "
|
||||
+ "<a href=\"appSettings\" style=\"text-decoration:none; color:%1;\">settings must be reset</a>."
|
||||
+ "\nEncrypted files can only be opened with password used to encrypt them").arg(AmneziaStyle.color.goldenApricot)
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
|
||||
onLinkActivated: function(link) {
|
||||
if (link === "appSettings") {
|
||||
PageController.closePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isChangingPassword: false
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onChangingPassword() {
|
||||
root.isChangingPassword = true
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
|
||||
backButtonFunction: function() {
|
||||
PageController.closePage()
|
||||
if (root.isChangingPassword) {
|
||||
root.isChangingPassword = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: root.isChangingPassword ? qsTr("Change password") : qsTr("Password & Encryption")
|
||||
descriptionText: root.isChangingPassword ? qsTr("Existing encrypted files will still require the old password.\nThe new password will be used for new encrypted files.")
|
||||
: qsTr("Password protection for backups and configuration files.\nRequired to restore or import encrypted files.")
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
Layout.leftMargin: 8
|
||||
Layout.bottomMargin: 32
|
||||
implicitHeight: 16
|
||||
|
||||
defaultColor: AmneziaStyle.color.transparent
|
||||
hoveredColor: AmneziaStyle.color.translucentWhite
|
||||
pressedColor: AmneziaStyle.color.sheerWhite
|
||||
disabledColor: AmneziaStyle.color.mutedGray
|
||||
textColor: AmneziaStyle.color.goldenApricot
|
||||
|
||||
text: qsTr("Learn more")
|
||||
|
||||
clickedFunc: function() {
|
||||
Qt.openUrlExternally("https://storage.googleapis.com/amnezia/docs?m-path=/documentation/instructions/encryption")
|
||||
}
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: indicator
|
||||
|
||||
visible: !root.isChangingPassword
|
||||
|
||||
textString: qsTr("Password not set. Encryption disabled")
|
||||
iconPath: "qrc:/images/controls/lock-unlocked.svg"
|
||||
}
|
||||
}
|
||||
|
||||
model: inputFields
|
||||
spacing: 16
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: delegate
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: title
|
||||
textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal
|
||||
textField.placeholderText: placeholderContent
|
||||
textField.text: textField.text
|
||||
|
||||
rightButtonClickedOnEnter: true
|
||||
|
||||
clickedFunc: function () {
|
||||
clickedHandler()
|
||||
buttonImageSource = textField.text !== "" ? imageSource : ""
|
||||
}
|
||||
|
||||
textField.onFocusChanged: {
|
||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
|
||||
textField.onTextChanged: {
|
||||
buttonImageSource = textField.text !== "" ? imageSource : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 32
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Continue")
|
||||
|
||||
clickedFunc: function() {
|
||||
if (!root.isPasswordProperlyFilled()) {
|
||||
return
|
||||
}
|
||||
|
||||
var _password = listView.itemAtIndex(vars.passwordIndex).children[0].textField.text
|
||||
var _hint = listView.itemAtIndex(vars.hintIndex).children[0].textField.text
|
||||
|
||||
SettingsController.setTempPassword(_password)
|
||||
SettingsController.setTempHint(_hint)
|
||||
|
||||
PageController.goToPage(PageEnum.PageSettingsAppPasswordConfirm)
|
||||
if (root.isChangingPassword) {
|
||||
SettingsController.changingPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPasswordProperlyFilled() {
|
||||
var tooShort = false
|
||||
|
||||
var secretDataItem = listView.itemAtIndex(vars.passwordIndex).children[0]
|
||||
if (secretDataItem.textField.text === "") {
|
||||
secretDataItem.errorText = qsTr("Password cannot be empty")
|
||||
tooShort = true
|
||||
} else if (secretDataItem.textField.text.length < 4) {
|
||||
secretDataItem.errorText = qsTr("Password too short")
|
||||
tooShort = true
|
||||
}
|
||||
|
||||
return !tooShort
|
||||
}
|
||||
|
||||
property list<QtObject> inputFields: [
|
||||
passwordObject,
|
||||
hintObject
|
||||
]
|
||||
|
||||
QtObject {
|
||||
id: passwordObject
|
||||
|
||||
property string title: root.isChangingPassword ? qsTr("New password") : qsTr("Set encryption password")
|
||||
readonly property string placeholderContent: ""
|
||||
property string imageSource: "qrc:/images/controls/eye.svg"
|
||||
property bool hideContent: true
|
||||
readonly property var clickedHandler: function() {
|
||||
hideContent = !hideContent
|
||||
imageSource = hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg"
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: hintObject
|
||||
|
||||
property string title: root.isChangingPassword ? qsTr("New password hint (optional)") : qsTr("Password hint")
|
||||
readonly property string placeholderContent: ""
|
||||
property string imageSource: ""
|
||||
property bool hideContent: false
|
||||
readonly property var clickedHandler: undefined
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: vars
|
||||
|
||||
readonly property int passwordIndex: 0
|
||||
readonly property int hintIndex: 1
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import PageEnum 1.0
|
||||
import Style 1.0
|
||||
|
||||
import "./"
|
||||
import "../Controls2"
|
||||
import "../Config"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool isChangingPassword: false
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onChangingPassword() {
|
||||
root.isChangingPassword = true
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20
|
||||
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListViewType {
|
||||
id: listView
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
header: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
BaseHeaderType {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: root.isChangingPassword ? qsTr("Confirm new password") : qsTr("Confirm password")
|
||||
descriptionText: root.isChangingPassword ? qsTr("") : qsTr("If the password is forgotten, it cant be recovered. "
|
||||
+ "To reset the password, the app settings must be reset.\n"
|
||||
+ "Encrypted files can only be opened with the password used to encrypt them")
|
||||
}
|
||||
}
|
||||
|
||||
model: 1 // fake model
|
||||
spacing: 16
|
||||
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: delegate
|
||||
|
||||
property bool hideContent: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: root.isChangingPassword ? qsTr("Re-enter new password") : qsTr("Re-enter password")
|
||||
textField.echoMode: hideContent ? TextInput.Password : TextInput.Normal
|
||||
textField.placeholderText: ""
|
||||
textField.text: textField.text
|
||||
|
||||
rightButtonClickedOnEnter: true
|
||||
|
||||
clickedFunc: function () {
|
||||
hideContent = !hideContent
|
||||
buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
|
||||
}
|
||||
|
||||
textField.onFocusChanged: {
|
||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
|
||||
textField.onTextChanged: {
|
||||
buttonImageSource = textField.text !== "" ? (hideContent ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
LabelTextType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 24
|
||||
|
||||
text: qsTr("Hint: ") + SettingsController.getTempHint()
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
id: continueButton
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: root.isChangingPassword ? qsTr("Save new password") : qsTr("Turn on encryption")
|
||||
|
||||
clickedFunc: function() {
|
||||
if (!root.isPasswordProperlyFilled()) {
|
||||
return
|
||||
}
|
||||
|
||||
SettingsController.setPassword(SettingsController.getTempPassword())
|
||||
SettingsController.setHint(SettingsController.getTempHint())
|
||||
|
||||
SettingsController.setTempPassword("")
|
||||
SettingsController.setTempHint("")
|
||||
|
||||
PageController.closePage()
|
||||
PageController.goToPage(PageEnum.PageSettings)
|
||||
PageController.goToPage(PageEnum.PageSettingsAppEncryption)
|
||||
SettingsController.toggleFileEncryption(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isPasswordProperlyFilled() {
|
||||
var notMatch = false
|
||||
|
||||
var secretDataItem = listView.itemAtIndex(0).children[0]
|
||||
if (secretDataItem.textField.text !== SettingsController.getTempPassword()) {
|
||||
secretDataItem.errorText = qsTr("Passwords not match")
|
||||
notMatch = true
|
||||
}
|
||||
|
||||
return !notMatch
|
||||
}
|
||||
}
|
||||
@@ -213,23 +213,6 @@ PageType {
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: labelWithButtonAppPassword
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Password & Encryption")
|
||||
descriptionText: qsTr("Password protection for backups and configuration files")
|
||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||
|
||||
clickedFunction: function() {
|
||||
SettingsController.getPassword() === "" ? PageController.goToPage(PageEnum.PageSettingsAppPassword)
|
||||
: PageController.goToPage(PageEnum.PageSettingsAppEncryption)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {}
|
||||
|
||||
LabelWithButtonType {
|
||||
id: labelWithButtonLogging
|
||||
|
||||
|
||||
@@ -67,16 +67,6 @@ PageType {
|
||||
headerText: qsTr("Back up your configuration")
|
||||
descriptionText: qsTr("You can save your settings to a backup file to restore them the next time you install the application.")
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: indicator
|
||||
|
||||
visible: SettingsController.isFileEncryptionEnabled()
|
||||
linkEnabled: true
|
||||
|
||||
textString: qsTr("Encryption enabled.")
|
||||
iconPath: "qrc:/images/controls/lock-locked.svg"
|
||||
}
|
||||
}
|
||||
|
||||
model: 1 // fake model to force the ListView to be created without a model
|
||||
@@ -150,20 +140,10 @@ PageType {
|
||||
var filePath = SystemController.getFileName(qsTr("Open backup file"),
|
||||
qsTr("Backup files (*.backup)"))
|
||||
if (filePath !== "") {
|
||||
passwordDrawer.fileName = filePath
|
||||
SystemController.isFileEncrypted(filePath) ? passwordDrawer.restoreSecuredBackup() : restoreBackup(filePath)
|
||||
restoreBackup(filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PasswordDrawer {
|
||||
id: passwordDrawer
|
||||
|
||||
parent: root
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.45
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
|
||||
headerText: qsTr("Logging")
|
||||
descriptionText: qsTr("Logs help diagnose app errors and connection issues" +
|
||||
"Logging is disabled by default. Enable it when troubleshooting or if requested by support")
|
||||
descriptionText: qsTr("Enabling this function will save application's logs automatically. " +
|
||||
"By default, logging functionality is disabled. Enable log saving in case of application malfunction.")
|
||||
}
|
||||
|
||||
SwitcherType {
|
||||
@@ -60,7 +60,7 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
|
||||
text: qsTr("Enable logging")
|
||||
text: qsTr("Enable logs")
|
||||
|
||||
checked: SettingsController.isLoggingEnabled
|
||||
|
||||
@@ -77,7 +77,7 @@ PageType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -8
|
||||
|
||||
text: qsTr("Delete all logs")
|
||||
text: qsTr("Clear logs")
|
||||
leftImageSource: "qrc:/images/controls/trash.svg"
|
||||
isSmallLeftImage: true
|
||||
|
||||
@@ -154,7 +154,7 @@ PageType {
|
||||
Layout.topMargin: -8
|
||||
Layout.bottomMargin: -8
|
||||
|
||||
text: qsTr("Save logs to file")
|
||||
text: qsTr("Export logs")
|
||||
leftImageSource: "qrc:/images/controls/save.svg"
|
||||
isSmallLeftImage: true
|
||||
|
||||
@@ -178,7 +178,7 @@ PageType {
|
||||
QtObject {
|
||||
id: clientLogs
|
||||
|
||||
readonly property string title: qsTr("App logs")
|
||||
readonly property string title: qsTr("Client logs")
|
||||
readonly property string description: qsTr("AmneziaVPN logs")
|
||||
readonly property bool isVisible: true
|
||||
readonly property var openLogsHandler: function() {
|
||||
|
||||
@@ -12,7 +12,6 @@ import "./"
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
import "../Config"
|
||||
import "../Components"
|
||||
|
||||
PageType {
|
||||
id: root
|
||||
@@ -265,15 +264,6 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
PasswordDrawer {
|
||||
id: passwordDrawer
|
||||
|
||||
parent: root
|
||||
|
||||
anchors.fill: parent
|
||||
expandedHeight: root.height * 0.45
|
||||
}
|
||||
|
||||
property list<QtObject> variants: [
|
||||
amneziaVpn,
|
||||
selfHostVpn,
|
||||
@@ -328,12 +318,7 @@ PageType {
|
||||
qsTr("Backup files (*.backup)"))
|
||||
if (filePath !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
if (SystemController.isFileEncrypted(filePath)) {
|
||||
passwordDrawer.fileName = filePath
|
||||
passwordDrawer.restoreSecuredBackup()
|
||||
} else {
|
||||
SettingsController.restoreAppConfig(filePath)
|
||||
}
|
||||
SettingsController.restoreAppConfig(filePath)
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
@@ -351,13 +336,8 @@ PageType {
|
||||
var nameFilter = "Config files (*.vpn *.ovpn *.conf *.json)"
|
||||
var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter)
|
||||
if (fileName !== "") {
|
||||
if (SystemController.isFileEncrypted(fileName)) {
|
||||
passwordDrawer.fileName = fileName
|
||||
passwordDrawer.importSecuredFile()
|
||||
} else {
|
||||
if (ImportController.extractConfigFromFile(fileName)) {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
|
||||
}
|
||||
if (ImportController.extractConfigFromFile(fileName)) {
|
||||
PageController.goToPage(PageEnum.PageSetupWizardViewConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,16 +271,6 @@ PageType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: indicator
|
||||
|
||||
visible: SettingsController.isFileEncryptionEnabled()
|
||||
linkEnabled: true
|
||||
|
||||
textString: qsTr("Encryption enabled.")
|
||||
iconPath: "qrc:/images/controls/lock-locked.svg"
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: clientNameTextField
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -108,8 +108,6 @@ PageType {
|
||||
if (fileName !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
ExportController.exportConfig(fileName)
|
||||
if (SettingsController.isFileEncryptionEnabled())
|
||||
SystemController.QEncryptFile(fileName, SettingsController.getPassword(), SettingsController.getHint())
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,16 +69,6 @@ PageType {
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
id: indicator
|
||||
|
||||
visible: SettingsController.isFileEncryptionEnabled()
|
||||
linkEnabled: true
|
||||
|
||||
textString: qsTr("Encryption enabled.")
|
||||
iconPath: "qrc:/images/controls/lock-locked.svg"
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: serverSelector
|
||||
objectName: "serverSelector"
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
<file>Components/HomeContainersListView.qml</file>
|
||||
<file>Components/HomeSplitTunnelingDrawer.qml</file>
|
||||
<file>Components/InstalledAppsDrawer.qml</file>
|
||||
<file>Components/PasswordDrawer.qml</file>
|
||||
<file>Components/ChangelogDrawer.qml</file>
|
||||
<file>Components/QuestionDrawer.qml</file>
|
||||
<file>Components/SelectLanguageDrawer.qml</file>
|
||||
<file>Components/ServersListView.qml</file>
|
||||
<file>Components/SettingsContainersListView.qml</file>
|
||||
<file>Components/EncryptionIndicator.qml</file>
|
||||
<file>Components/BenefitRow.qml</file>
|
||||
<file>Components/BenefitsPanel.qml</file>
|
||||
<file>Components/SubscriptionExpiredDrawer.qml</file>
|
||||
@@ -101,9 +99,6 @@
|
||||
<file>Pages2/PageSettingsApiServerInfo.qml</file>
|
||||
<file>Pages2/PageSettingsApplication.qml</file>
|
||||
<file>Pages2/PageSettingsAppSplitTunneling.qml</file>
|
||||
<file>Pages2/PageSettingsAppEncryption.qml</file>
|
||||
<file>Pages2/PageSettingsAppPassword.qml</file>
|
||||
<file>Pages2/PageSettingsAppPasswordConfirm.qml</file>
|
||||
<file>Pages2/PageSettingsBackup.qml</file>
|
||||
<file>Pages2/PageSettingsConnection.qml</file>
|
||||
<file>Pages2/PageSettingsDns.qml</file>
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
#include <QDebug>
|
||||
#include "notificationHandler.h"
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID)
|
||||
# include "platforms/android/android_notificationhandler.h"
|
||||
#elif defined(Q_OS_IOS)
|
||||
# include "platforms/ios/iosnotificationhandler.h"
|
||||
#else
|
||||
# include "systemTrayNotificationHandler.h"
|
||||
#endif
|
||||
|
||||
|
||||
// static
|
||||
NotificationHandler* NotificationHandler::create(QObject* parent) {
|
||||
#if defined(Q_OS_IOS)
|
||||
#if defined(Q_OS_ANDROID)
|
||||
return new AndroidNotificationHandler(parent);
|
||||
#elif defined(Q_OS_IOS)
|
||||
return new IOSNotificationHandler(parent);
|
||||
#else
|
||||
return new SystemTrayNotificationHandler(parent);
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
# include "platforms/macos/macosutils.h"
|
||||
#endif
|
||||
|
||||
#ifdef MACOS_NE
|
||||
# include "platforms/macos/macos_ne_vpn_notification.h"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QIcon>
|
||||
@@ -152,6 +156,12 @@ void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
|
||||
int timerMsec) {
|
||||
Q_UNUSED(type);
|
||||
|
||||
#ifdef MACOS_NE
|
||||
Q_UNUSED(timerMsec);
|
||||
macosNePostVpnStateNotification(title, message);
|
||||
return;
|
||||
#endif
|
||||
|
||||
QIcon icon(ConnectedTrayIconName);
|
||||
m_systemTrayIcon.showMessage(title, message, icon, timerMsec);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
set(CPACK_PACKAGE_VENDOR AmneziaVPN)
|
||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
||||
if(WIN32)
|
||||
set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_windows_x64")
|
||||
elseif(APPLE AND NOT IOS AND NOT MACOS_NE)
|
||||
set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_macos_x64")
|
||||
elseif(LINUX AND NOT ANDROID)
|
||||
set(CPACK_PACKAGE_FILE_NAME "AmneziaVPN_${AMNEZIAVPN_VERSION}_linux_x64")
|
||||
endif()
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY AmneziaVPN)
|
||||
set(CPACK_PACKAGE_EXECUTABLES AmneziaVPN AmneziaVPN)
|
||||
set(CPACK_PRE_BUILD_SCRIPTS ${CMAKE_CURRENT_LIST_DIR}/sign_binaries.cmake)
|
||||
|
||||
@@ -84,10 +84,6 @@ function(detect_os os os_api_level os_sdk os_subsystem os_version)
|
||||
set(_os_sdk "watch${apple_platform_suffix}")
|
||||
endif()
|
||||
endif()
|
||||
# Macos does not support os.sdk
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(_os_sdk "")
|
||||
endif()
|
||||
if(DEFINED os_sdk)
|
||||
message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}")
|
||||
set(${os_sdk} ${_os_sdk} PARENT_SCOPE)
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ class AmneziaVPN(ConanFile):
|
||||
|
||||
if has_ne:
|
||||
self.requires("awg-apple/2.0.1")
|
||||
self.requires("hev-socks5-tunnel/2.15.0", options={"as_framework": True})
|
||||
self.requires("hev-socks5-tunnel/2.14.4", options={"as_framework": True})
|
||||
self.requires("openvpnadapter/1.0.0")
|
||||
|
||||
if os == "Android":
|
||||
|
||||
+3
-3
@@ -38,9 +38,9 @@ download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86.apk
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android9+_x86_64.apk
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.run
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos_x64.pkg
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_windows_x64.exe
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.pkg
|
||||
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_x64.exe
|
||||
|
||||
cd ../
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from conan import ConanFile
|
||||
from conan.tools.files import get, copy
|
||||
from conan.tools.layout import basic_layout
|
||||
from conan.errors import ConanInvalidConfiguration
|
||||
from conan.tools.env import Environment
|
||||
from conan.tools.env import VirtualBuildEnv, Environment
|
||||
|
||||
import os
|
||||
import stat
|
||||
@@ -34,6 +34,7 @@ class AmneziaLibxray(ConanFile):
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
VirtualBuildEnv(self).generate()
|
||||
env = Environment()
|
||||
ndk_path_str = self.conf.get("tools.android:ndk_path")
|
||||
if ndk_path_str:
|
||||
|
||||
@@ -15,7 +15,7 @@ required_conan_version = ">=2.26"
|
||||
|
||||
class HevSocks5Tunnel(ConanFile):
|
||||
name = "hev-socks5-tunnel"
|
||||
version = "2.15.0"
|
||||
version = "2.14.4"
|
||||
settings = "os", "arch", "compiler"
|
||||
options = {
|
||||
"shared": [True, False],
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
patches:
|
||||
"2.7.0":
|
||||
- patch_file: "patches/0001-carefully-handle-CMAKE_GENERATOR_PLATFORM.patch"
|
||||
- patch_file: "patches/0002-explicitly-pass-unicode-everywhere.patch"
|
||||
@@ -1,5 +1,5 @@
|
||||
from conan import ConanFile
|
||||
from conan.tools.files import get, copy, replace_in_file, apply_conandata_patches, export_conandata_patches
|
||||
from conan.tools.files import get, copy, replace_in_file
|
||||
from conan.tools.gnu import Autotools, AutotoolsToolchain, AutotoolsDeps, PkgConfigDeps
|
||||
from conan.tools.layout import basic_layout
|
||||
from conan.tools.cmake import cmake_layout, CMakeToolchain, CMake, CMakeDeps
|
||||
@@ -17,7 +17,6 @@ class Openvpn(ConanFile):
|
||||
return str(self.settings.os).startswith("Windows")
|
||||
|
||||
def export_sources(self):
|
||||
export_conandata_patches(self)
|
||||
copy(self, "*applink.c", src=self.recipe_folder, dst=self.export_sources_folder)
|
||||
|
||||
def layout(self):
|
||||
@@ -85,7 +84,6 @@ class Openvpn(ConanFile):
|
||||
deps.generate()
|
||||
|
||||
def build(self):
|
||||
apply_conandata_patches(self)
|
||||
if self._is_windows:
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
From 693bee38daaec5962ea3f0939c71e869f202c08a Mon Sep 17 00:00:00 2001
|
||||
From: Yaroslav Gurov <ygurov@proton.me>
|
||||
Date: Mon, 18 May 2026 16:58:00 +0200
|
||||
Subject: [PATCH] carefully handle CMAKE_GENERATOR_PLATFORM
|
||||
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 198c98ff..7341db70 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -108,7 +108,7 @@ if (MSVC)
|
||||
"$<$<CONFIG:Release>:/OPT:REF>"
|
||||
"$<$<CONFIG:Release>:/OPT:ICF>"
|
||||
)
|
||||
- if (${CMAKE_GENERATOR_PLATFORM} STREQUAL "x64" OR ${CMAKE_GENERATOR_PLATFORM} STREQUAL "x86")
|
||||
+ if ("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64" OR "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x86")
|
||||
add_link_options("$<$<CONFIG:Release>:/CETCOMPAT>")
|
||||
endif()
|
||||
else ()
|
||||
--
|
||||
2.46.0.windows.1
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
From 9a42a0350abaa1a329ad56e40b1900ef78183323 Mon Sep 17 00:00:00 2001
|
||||
From: Yaroslav Gurov <ygurov@proton.me>
|
||||
Date: Mon, 18 May 2026 18:05:09 +0200
|
||||
Subject: [PATCH] explicitly pass unicode everywhere
|
||||
|
||||
---
|
||||
src/openvpnmsica/CMakeLists.txt | 1 +
|
||||
src/openvpnserv/CMakeLists.txt | 1 +
|
||||
src/tapctl/CMakeLists.txt | 1 +
|
||||
3 files changed, 3 insertions(+)
|
||||
|
||||
diff --git a/src/openvpnmsica/CMakeLists.txt b/src/openvpnmsica/CMakeLists.txt
|
||||
index 9126b80f..23f979d6 100644
|
||||
--- a/src/openvpnmsica/CMakeLists.txt
|
||||
+++ b/src/openvpnmsica/CMakeLists.txt
|
||||
@@ -22,6 +22,7 @@ target_sources(openvpnmsica PRIVATE
|
||||
openvpnmsica_resources.rc
|
||||
)
|
||||
target_compile_options(openvpnmsica PRIVATE
|
||||
+ -DUNICODE
|
||||
-D_UNICODE
|
||||
-UNTDDI_VERSION
|
||||
-D_WIN32_WINNT=_WIN32_WINNT_VISTA
|
||||
diff --git a/src/openvpnserv/CMakeLists.txt b/src/openvpnserv/CMakeLists.txt
|
||||
index fc153822..b3a0cff1 100644
|
||||
--- a/src/openvpnserv/CMakeLists.txt
|
||||
+++ b/src/openvpnserv/CMakeLists.txt
|
||||
@@ -19,6 +19,7 @@ function(add_common_options target)
|
||||
${MC_GEN_DIR}
|
||||
)
|
||||
target_compile_options(${target} PRIVATE
|
||||
+ -DUNICODE
|
||||
-D_UNICODE
|
||||
-UNTDDI_VERSION
|
||||
-D_WIN32_WINNT=_WIN32_WINNT_VISTA
|
||||
diff --git a/src/tapctl/CMakeLists.txt b/src/tapctl/CMakeLists.txt
|
||||
index 97702c01..81da46b8 100644
|
||||
--- a/src/tapctl/CMakeLists.txt
|
||||
+++ b/src/tapctl/CMakeLists.txt
|
||||
@@ -19,6 +19,7 @@ target_sources(tapctl PRIVATE
|
||||
tapctl_resources.rc
|
||||
)
|
||||
target_compile_options(tapctl PRIVATE
|
||||
+ -DUNICODE
|
||||
-D_UNICODE
|
||||
-UNTDDI_VERSION
|
||||
-D_WIN32_WINNT=_WIN32_WINNT_VISTA
|
||||
--
|
||||
2.46.0.windows.1
|
||||
|
||||
Reference in New Issue
Block a user