Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/desktop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudio Cambra <claudio.cambra@gmail.com>2022-05-10 17:12:15 +0300
committerClaudio Cambra <claudio.cambra@gmail.com>2022-05-12 13:09:39 +0300
commit99420fd7ff44d4aab221390a03f22e07418c308b (patch)
tree1df6967f6a462a4efaed2937ffa097b20716efba
parent4aac60814da6555be7e9cdc70fddd3c9270cbd87 (diff)
Reimplement notifications for macOS and add support for actionable update notificationsfeature/mac-notifications-3.4
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
-rw-r--r--src/gui/CMakeLists.txt2
-rw-r--r--src/gui/application.cpp2
-rw-r--r--src/gui/owncloudgui.cpp9
-rw-r--r--src/gui/owncloudgui.h1
-rw-r--r--src/gui/systray.cpp16
-rw-r--r--src/gui/systray.h7
-rw-r--r--src/gui/systray.mm137
-rw-r--r--src/gui/updater/ocupdater.cpp2
-rw-r--r--src/gui/updater/ocupdater.h4
9 files changed, 157 insertions, 23 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 6b63f4630..0fe4e5c6b 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -565,7 +565,7 @@ endif()
if (APPLE)
find_package(Qt5 COMPONENTS MacExtras)
- target_link_libraries(nextcloudCore PUBLIC Qt5::MacExtras)
+ target_link_libraries(nextcloudCore PUBLIC Qt5::MacExtras "-framework UserNotifications")
endif()
if(WITH_CRASHREPORTER)
diff --git a/src/gui/application.cpp b/src/gui/application.cpp
index e6dd39142..f43b2a598 100644
--- a/src/gui/application.cpp
+++ b/src/gui/application.cpp
@@ -372,7 +372,7 @@ Application::Application(int &argc, char **argv)
// Update checks
auto *updaterScheduler = new UpdaterScheduler(this);
connect(updaterScheduler, &UpdaterScheduler::updaterAnnouncement,
- _gui.data(), &ownCloudGui::slotShowTrayMessage);
+ _gui.data(), &ownCloudGui::slotShowTrayUpdateMessage);
connect(updaterScheduler, &UpdaterScheduler::requestRestart,
_folderManager.data(), &FolderMan::slotScheduleAppRestart);
#endif
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 3941d9b0f..61739799e 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -379,6 +379,15 @@ void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
qCWarning(lcApplication) << "Tray not ready: " << msg;
}
+void ownCloudGui::slotShowTrayUpdateMessage(const QString &title, const QString &msg, const QUrl &webUrl)
+{
+ if(_tray) {
+ _tray->showUpdateMessage(title, msg, webUrl);
+ } else {
+ qCWarning(lcApplication) << "Tray not ready: " << msg;
+ }
+}
+
void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QString &msg)
{
slotShowTrayMessage(title, msg);
diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h
index 312c43748..3ffa57cd1 100644
--- a/src/gui/owncloudgui.h
+++ b/src/gui/owncloudgui.h
@@ -75,6 +75,7 @@ signals:
public slots:
void slotComputeOverallSyncStatus();
void slotShowTrayMessage(const QString &title, const QString &msg);
+ void slotShowTrayUpdateMessage(const QString &title, const QString &msg, const QUrl &webUrl);
void slotShowOptionalTrayMessage(const QString &title, const QString &msg);
void slotFolderOpenAction(const QString &alias);
void slotUpdateProgress(const QString &folder, const ProgressInfo &progress);
diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp
index 707609612..dd0eed2ed 100644
--- a/src/gui/systray.cpp
+++ b/src/gui/systray.cpp
@@ -97,7 +97,11 @@ Systray::Systray()
qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
-#ifndef Q_OS_MAC
+#ifdef Q_OS_MACOS
+ setUserNotificationCenterDelegate();
+ checkNotificationAuth();
+ registerNotificationCategories(QString(tr("Download")));
+#else
auto contextMenu = new QMenu();
if (AccountManager::instance()->accounts().isEmpty()) {
contextMenu->addAction(tr("Add account"), this, &Systray::openAccountWizard);
@@ -255,6 +259,16 @@ void Systray::showMessage(const QString &title, const QString &message, MessageI
}
}
+void Systray::showUpdateMessage(const QString &title, const QString &message, const QUrl &webUrl)
+{
+#ifdef Q_OS_MACOS
+ sendOsXUpdateNotification(title, message, webUrl);
+#else // TODO: Implement custom notifications (i.e. actionable) for other OSes
+ Q_UNUSED(webUrl);
+ showMessage(title, message);
+#endif
+}
+
void Systray::setToolTip(const QString &tip)
{
QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
diff --git a/src/gui/systray.h b/src/gui/systray.h
index 31251d01e..9eabdf4a1 100644
--- a/src/gui/systray.h
+++ b/src/gui/systray.h
@@ -38,9 +38,13 @@ public:
QNetworkAccessManager* create(QObject *parent) override;
};
-#ifdef Q_OS_OSX
+#ifdef Q_OS_MACOS
+void setUserNotificationCenterDelegate();
+void checkNotificationAuth();
+void registerNotificationCategories(const QString &localizedDownloadString);
bool canOsXSendUserNotification();
void sendOsXUserNotification(const QString &title, const QString &message);
+void sendOsXUpdateNotification(const QString &title, const QString &message, const QUrl &webUrl);
void setTrayWindowLevelAndVisibleOnAllSpaces(QWindow *window);
#endif
@@ -66,6 +70,7 @@ public:
void setTrayEngine(QQmlApplicationEngine *trayEngine);
void create();
void showMessage(const QString &title, const QString &message, MessageIcon icon = Information);
+ void showUpdateMessage(const QString &title, const QString &message, const QUrl &webUrl);
void setToolTip(const QString &tip);
bool isOpen();
QString windowTitle() const;
diff --git a/src/gui/systray.mm b/src/gui/systray.mm
index a4d35eb23..dfdc009cf 100644
--- a/src/gui/systray.mm
+++ b/src/gui/systray.mm
@@ -1,45 +1,149 @@
+#include "QtCore/qurl.h"
+#include "config.h"
#include <QString>
#include <QWindow>
+#include <QLoggingCategory>
+
#import <Cocoa/Cocoa.h>
+#import <UserNotifications/UserNotifications.h>
+
+Q_LOGGING_CATEGORY(lcMacSystray, "nextcloud.gui.macsystray")
@interface NotificationCenterDelegate : NSObject
@end
@implementation NotificationCenterDelegate
+
// Always show, even if app is active at the moment.
-- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center
- shouldPresentNotification:(NSUserNotification *)notification
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ willPresentNotification:(UNNotification *)notification
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
- Q_UNUSED(center);
- Q_UNUSED(notification);
- return YES;
+ completionHandler(UNNotificationPresentationOptionSound + UNNotificationPresentationOptionBanner);
+}
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ didReceiveNotificationResponse:(UNNotificationResponse *)response
+ withCompletionHandler:(void (^)(void))completionHandler
+{
+ qCDebug(lcMacSystray()) << "Received notification with category identifier:" << response.notification.request.content.categoryIdentifier
+ << "and action identifier" << response.actionIdentifier;
+ UNNotificationContent* content = response.notification.request.content;
+ if ([content.categoryIdentifier isEqualToString:@"UPDATE"]) {
+
+ if ([response.actionIdentifier isEqualToString:@"DOWNLOAD_ACTION"] || [response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier])
+ {
+ qCDebug(lcMacSystray()) << "Opening update download url in browser.";
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[content.userInfo objectForKey:@"webUrl"]]];
+ }
+ }
+
+ completionHandler();
}
@end
namespace OCC {
+double statusBarThickness()
+{
+ return [NSStatusBar systemStatusBar].thickness;
+}
+
+// TODO: Get this to actually check for permissions
bool canOsXSendUserNotification()
{
- return NSClassFromString(@"NSUserNotificationCenter") != nil;
+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
+ return center != nil;
}
-void sendOsXUserNotification(const QString &title, const QString &message)
+void registerNotificationCategories(const QString &localisedDownloadString) {
+ UNNotificationCategory* generalCategory = [UNNotificationCategory
+ categoryWithIdentifier:@"GENERAL"
+ actions:@[]
+ intentIdentifiers:@[]
+ options:UNNotificationCategoryOptionCustomDismissAction];
+
+ // Create the custom actions for update notifications.
+ UNNotificationAction* downloadAction = [UNNotificationAction
+ actionWithIdentifier:@"DOWNLOAD_ACTION"
+ title:localisedDownloadString.toNSString()
+ options:UNNotificationActionOptionNone];
+
+ // Create the category with the custom actions.
+ UNNotificationCategory* updateCategory = [UNNotificationCategory
+ categoryWithIdentifier:@"UPDATE"
+ actions:@[downloadAction]
+ intentIdentifiers:@[]
+ options:UNNotificationCategoryOptionNone];
+
+ [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObjects:generalCategory, updateCategory, nil]];
+}
+
+void checkNotificationAuth()
+{
+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
+ [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound + UNAuthorizationOptionProvisional)
+ completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ // Enable or disable features based on authorization.
+ if(granted) {
+ qCDebug(lcMacSystray) << "Authorization for notifications has been granted, can display notifications.";
+ } else {
+ qCDebug(lcMacSystray) << "Authorization for notifications not granted.";
+ if(error) {
+ QString errorDescription([error.localizedDescription UTF8String]);
+ qCDebug(lcMacSystray) << "Error from notification center: " << errorDescription;
+ }
+ }
+ }];
+}
+
+void setUserNotificationCenterDelegate()
{
- Class cuserNotificationCenter = NSClassFromString(@"NSUserNotificationCenter");
- id userNotificationCenter = [cuserNotificationCenter defaultUserNotificationCenter];
+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
static dispatch_once_t once;
dispatch_once(&once, ^{
id delegate = [[NotificationCenterDelegate alloc] init];
- [userNotificationCenter setDelegate:delegate];
+ [center setDelegate:delegate];
});
+}
+
+UNMutableNotificationContent* basicNotificationContent(const QString &title, const QString &message)
+{
+ UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
+ content.title = title.toNSString();
+ content.body = message.toNSString();
+ content.sound = [UNNotificationSound defaultSound];
+
+ return content;
+}
+
+void sendOsXUserNotification(const QString &title, const QString &message)
+{
+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
+ checkNotificationAuth();
+
+ UNMutableNotificationContent* content = basicNotificationContent(title, message);
+ content.categoryIdentifier = @"GENERAL";
- Class cuserNotification = NSClassFromString(@"NSUserNotification");
- id notification = [[cuserNotification alloc] init];
- [notification setTitle:[NSString stringWithUTF8String:title.toUtf8().data()]];
- [notification setInformativeText:[NSString stringWithUTF8String:message.toUtf8().data()]];
+ UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats: NO];
+ UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"NCUserNotification" content:content trigger:trigger];
- [userNotificationCenter deliverNotification:notification];
- [notification release];
+ [center addNotificationRequest:request withCompletionHandler:nil];
+}
+
+void sendOsXUpdateNotification(const QString &title, const QString &message, const QUrl &webUrl)
+{
+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
+ checkNotificationAuth();
+
+ UNMutableNotificationContent* content = basicNotificationContent(title, message);
+ content.categoryIdentifier = @"UPDATE";
+ content.userInfo = [NSDictionary dictionaryWithObject:[webUrl.toNSURL() absoluteString] forKey:@"webUrl"];
+
+ UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats: NO];
+ UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"NCUpdateNotification" content:content trigger:trigger];
+
+ [center addNotificationRequest:request withCompletionHandler:nil];
}
void setTrayWindowLevelAndVisibleOnAllSpaces(QWindow *window)
@@ -52,3 +156,4 @@ void setTrayWindowLevelAndVisibleOnAllSpaces(QWindow *window)
}
}
+
diff --git a/src/gui/updater/ocupdater.cpp b/src/gui/updater/ocupdater.cpp
index 6bcf726d0..aecc66c90 100644
--- a/src/gui/updater/ocupdater.cpp
+++ b/src/gui/updater/ocupdater.cpp
@@ -193,7 +193,7 @@ void OCUpdater::setDownloadState(DownloadState state)
// or once for system based updates.
if (_state == OCUpdater::DownloadComplete || (oldState != OCUpdater::UpdateOnlyAvailableThroughSystem
&& _state == OCUpdater::UpdateOnlyAvailableThroughSystem)) {
- emit newUpdateAvailable(tr("Update Check"), statusString());
+ emit newUpdateAvailable(tr("Update Check"), statusString(), _updateInfo.web());
}
}
diff --git a/src/gui/updater/ocupdater.h b/src/gui/updater/ocupdater.h
index c6c1ad8df..15680f798 100644
--- a/src/gui/updater/ocupdater.h
+++ b/src/gui/updater/ocupdater.h
@@ -71,7 +71,7 @@ public:
UpdaterScheduler(QObject *parent);
signals:
- void updaterAnnouncement(const QString &title, const QString &msg);
+ void updaterAnnouncement(const QString &title, const QString &msg, const QUrl &webUrl);
void requestRestart();
private slots:
@@ -116,7 +116,7 @@ public:
signals:
void downloadStateChanged();
- void newUpdateAvailable(const QString &header, const QString &message);
+ void newUpdateAvailable(const QString &header, const QString &message, const QUrl &webUrl);
void requestRestart();
public slots: