mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Feat: Fixed push notifications on VPN connect/disconnect
This commit is contained in:
@@ -24,5 +24,8 @@
|
|||||||
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
||||||
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
||||||
|
|
||||||
|
<string name="vpnStateEventChannelName">Уведомления о VPN</string>
|
||||||
|
<string name="vpnStateEventChannelDescription">Краткие оповещения при подключении и отключении VPN</string>
|
||||||
|
|
||||||
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -24,5 +24,8 @@
|
|||||||
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
||||||
<string name="openNotificationSettings">Open notification 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>
|
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1006,6 +1006,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
|
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
|
||||||
|
|
||||||
|
/** Called from Qt (AndroidController) — CLI-570 VPN connect/disconnect heads-up. */
|
||||||
|
@Suppress("unused")
|
||||||
|
fun showVpnStateNotification(title: String, message: String) {
|
||||||
|
ServiceNotification.showVpnStateEvent(applicationContext, title, message)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun requestNotificationPermission() {
|
fun requestNotificationPermission() {
|
||||||
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
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"
|
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
|
||||||
const val NOTIFICATION_ID = 1337
|
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 GET_ACTIVITY_REQUEST_CODE = 0
|
||||||
private const val CONNECT_REQUEST_CODE = 1
|
private const val CONNECT_REQUEST_CODE = 1
|
||||||
private const val DISCONNECT_REQUEST_CODE = 2
|
private const val DISCONNECT_REQUEST_CODE = 2
|
||||||
@@ -162,8 +165,42 @@ class ServiceNotification(private val context: Context) {
|
|||||||
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
||||||
.build()
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ set(LIBS ${LIBS}
|
|||||||
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
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.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||||
@@ -43,6 +44,7 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
|
|||||||
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
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.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||||
|
|||||||
@@ -45,9 +45,12 @@ if(NOT IOS AND NOT MACOS_NE)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT ANDROID)
|
set(HEADERS ${HEADERS}
|
||||||
set(HEADERS ${HEADERS}
|
|
||||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
||||||
|
)
|
||||||
|
if(ANDROID)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -109,9 +112,12 @@ if(APPLE AND NOT IOS)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT ANDROID)
|
set(SOURCES ${SOURCES}
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||||
|
)
|
||||||
|
if(ANDROID)
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// SystemTrayNotificationHandler exists only on desktop + macOS NE builds (see client/cmake/sources.cmake).
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
#include "ui/systemtray_notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
#if defined(Q_OS_IOS)
|
||||||
#include "platforms/ios/ios_controller.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
#include <AmneziaVPN-Swift.h>
|
#include <AmneziaVPN-Swift.h>
|
||||||
@@ -242,7 +247,6 @@ void CoreController::initSignalHandlers()
|
|||||||
|
|
||||||
void CoreController::initNotificationHandler()
|
void CoreController::initNotificationHandler()
|
||||||
{
|
{
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
@@ -255,9 +259,11 @@ void CoreController::initNotificationHandler()
|
|||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
|
|
||||||
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
if (auto *trayHandler = qobject_cast<SystemTrayNotificationHandler *>(m_notificationHandler.get())) {
|
||||||
#endif
|
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::updateTranslator(const QLocale &locale)
|
void CoreController::updateTranslator(const QLocale &locale)
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#include "ui/notificationhandler.h"
|
||||||
#include "ui/systemtray_notificationhandler.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
@@ -51,10 +49,6 @@
|
|||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/newsModel.h"
|
#include "ui/models/newsModel.h"
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
#include "ui/notificationhandler.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class CoreController : public QObject
|
class CoreController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -101,9 +95,7 @@ private:
|
|||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QSharedPointer<QTranslator> m_translator;
|
QSharedPointer<QTranslator> m_translator;
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
#endif
|
|
||||||
|
|
||||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||||
|
|
||||||
|
|||||||
@@ -307,6 +307,16 @@ void AndroidController::requestNotificationPermission()
|
|||||||
callActivityMethod("requestNotificationPermission", "()V");
|
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()
|
bool AndroidController::requestAuthentication()
|
||||||
{
|
{
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public:
|
|||||||
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
||||||
bool isNotificationPermissionGranted();
|
bool isNotificationPermissionGranted();
|
||||||
void requestNotificationPermission();
|
void requestNotificationPermission();
|
||||||
|
void showVpnStateNotification(const QString &title, const QString &message);
|
||||||
bool requestAuthentication();
|
bool requestAuthentication();
|
||||||
void sendTouch(float x, float y);
|
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/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,8 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
|
|||||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||||
const QString& message, int timerMsec) {
|
const QString& message, int timerMsec) {
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
|
// timerMsec is tray display hint on Windows, not a schedule delay — was wrongly used as seconds (CLI-570).
|
||||||
|
Q_UNUSED(timerMsec);
|
||||||
|
|
||||||
if (!m_delegate) {
|
if (!m_delegate) {
|
||||||
return;
|
return;
|
||||||
@@ -71,11 +73,13 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
|||||||
content.body = message.toNSString();
|
content.body = message.toNSString();
|
||||||
content.sound = [UNNotificationSound defaultSound];
|
content.sound = [UNNotificationSound defaultSound];
|
||||||
|
|
||||||
int timerSec = timerMsec / 1000;
|
NSTimeInterval delay = 0.1;
|
||||||
UNTimeIntervalNotificationTrigger* trigger =
|
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
|
content:content
|
||||||
trigger:trigger];
|
trigger:trigger];
|
||||||
|
|
||||||
@@ -143,6 +147,7 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
|
|||||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||||
const QString& message, int timerMsec) {
|
const QString& message, int timerMsec) {
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
|
Q_UNUSED(timerMsec);
|
||||||
|
|
||||||
if (!m_delegate) {
|
if (!m_delegate) {
|
||||||
return;
|
return;
|
||||||
@@ -153,11 +158,13 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
|||||||
content.body = message.toNSString();
|
content.body = message.toNSString();
|
||||||
content.sound = [UNNotificationSound defaultSound];
|
content.sound = [UNNotificationSound defaultSound];
|
||||||
|
|
||||||
int timerSec = timerMsec / 1000;
|
NSTimeInterval delay = 0.1;
|
||||||
UNTimeIntervalNotificationTrigger* trigger =
|
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
|
content:content
|
||||||
trigger:trigger];
|
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);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
@@ -5,16 +5,19 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "notificationhandler.h"
|
#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"
|
# include "platforms/ios/iosnotificationhandler.h"
|
||||||
#else
|
#else
|
||||||
# include "systemtray_notificationhandler.h"
|
# include "systemtray_notificationhandler.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// static
|
// static
|
||||||
NotificationHandler* NotificationHandler::create(QObject* parent) {
|
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);
|
return new IOSNotificationHandler(parent);
|
||||||
#else
|
#else
|
||||||
return new SystemTrayNotificationHandler(parent);
|
return new SystemTrayNotificationHandler(parent);
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
# include "platforms/macos/macosutils.h"
|
# include "platforms/macos/macosutils.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef MACOS_NE
|
||||||
|
# include "platforms/macos/macos_ne_vpn_notification.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
@@ -152,6 +156,12 @@ void SystemTrayNotificationHandler::notify(NotificationHandler::Message type,
|
|||||||
int timerMsec) {
|
int timerMsec) {
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
|
|
||||||
|
#ifdef MACOS_NE
|
||||||
|
Q_UNUSED(timerMsec);
|
||||||
|
macosNePostVpnStateNotification(title, message);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
QIcon icon(ConnectedTrayIconName);
|
QIcon icon(ConnectedTrayIconName);
|
||||||
m_systemTrayIcon.showMessage(title, message, icon, timerMsec);
|
m_systemTrayIcon.showMessage(title, message, icon, timerMsec);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,17 @@ echo "Using Qt in $QT_BIN_DIR"
|
|||||||
echo "Using Android SDK in $ANDROID_SDK_ROOT"
|
echo "Using Android SDK in $ANDROID_SDK_ROOT"
|
||||||
echo "Using Android NDK in $ANDROID_NDK_ROOT"
|
echo "Using Android NDK in $ANDROID_NDK_ROOT"
|
||||||
|
|
||||||
|
if [[ -z "${ANDROID_NDK_ROOT:-}" ]]; then
|
||||||
|
echo "ANDROID_NDK_ROOT is not set. Example: export ANDROID_NDK_ROOT=\"\$HOME/Library/Android/sdk/ndk/<version>\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NDK_TOOLCHAIN="$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake"
|
||||||
|
if [[ ! -f "$NDK_TOOLCHAIN" ]]; then
|
||||||
|
echo "Missing NDK CMake toolchain: $NDK_TOOLCHAIN"
|
||||||
|
echo "Install a complete NDK (SDK Manager → NDK Side by side) or fix ANDROID_NDK_ROOT."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Run qt-cmake to configure build
|
# Run qt-cmake to configure build
|
||||||
qt_cmake_opts=()
|
qt_cmake_opts=()
|
||||||
|
|
||||||
@@ -106,6 +117,12 @@ else
|
|||||||
qt_cmake_opts+=(-DQT_ANDROID_ABIS="$ABIS")
|
qt_cmake_opts+=(-DQT_ANDROID_ABIS="$ABIS")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Pin SDK/NDK on every configure so CMakeCache does not keep a stale NDK after you change ANDROID_NDK_ROOT.
|
||||||
|
if [[ -n "${ANDROID_SDK_ROOT:-}" ]]; then
|
||||||
|
qt_cmake_opts+=(-DANDROID_SDK_ROOT="$ANDROID_SDK_ROOT")
|
||||||
|
fi
|
||||||
|
qt_cmake_opts+=(-DANDROID_NDK_ROOT="$ANDROID_NDK_ROOT")
|
||||||
|
|
||||||
# QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL=ON - Skip building apks as part of the default 'ALL' target
|
# QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL=ON - Skip building apks as part of the default 'ALL' target
|
||||||
# We'll build apks during androiddeployqt
|
# We'll build apks during androiddeployqt
|
||||||
$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -B $BUILD_DIR \
|
$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -B $BUILD_DIR \
|
||||||
|
|||||||
Reference in New Issue
Block a user