Index: src/CMakeLists.txt =================================================================== --- src/CMakeLists.txt +++ src/CMakeLists.txt @@ -41,6 +41,10 @@ ecm_qt_declare_logging_category(knotifications_SRCS HEADER debug_p.h IDENTIFIER LOG_KNOTIFICATIONS CATEGORY_NAME org.kde.knotifications) +if (APPLE) + list(APPEND knotifications_SRCS notifybymacosnotificationcenter.mm) +endif() + if (Canberra_FOUND) set(knotifications_SRCS ${knotifications_SRCS} notifybyaudio_canberra.cpp) @@ -117,7 +121,7 @@ endif() if(APPLE) - target_link_libraries(KF5Notifications PRIVATE Qt5::MacExtras) + target_link_libraries(KF5Notifications PRIVATE Qt5::MacExtras "-framework Foundation" "-framework AppKit") endif() if(X11_XTest_FOUND) Index: src/knotificationmanager.cpp =================================================================== --- src/knotificationmanager.cpp +++ src/knotificationmanager.cpp @@ -40,11 +40,13 @@ #include "notifybylogfile.h" #include "notifybytaskbar.h" #include "notifybyexecute.h" -#ifndef Q_OS_ANDROID +#ifdef Q_OS_ANDROID +#include "notifybyandroid.h" +#elif defined(Q_OS_MAC) +#include "notifybymacosnotificationcenter.h" +#else #include "notifybypopup.h" #include "notifybyportal.h" -#else -#include "notifybyandroid.h" #endif #include "debug_p.h" @@ -133,14 +135,16 @@ // We have a series of built-ins up first, and fall back to trying // to instantiate an externally supplied plugin. if (action == QLatin1String("Popup")) { -#ifndef Q_OS_ANDROID - if (d->portalDBusServiceExists) { - plugin = new NotifyByPortal(this); - } else { - plugin = new NotifyByPopup(this); - } -#else +#ifdef Q_OS_ANDROID plugin = new NotifyByAndroid(this); +#elif defined(Q_OS_MAC) + plugin = new NotifyByMacOSNotificationCenter(this); +#else + if (d->portalDBusServiceExists) { + plugin = new NotifyByPortal(this); + } else { + plugin = new NotifyByPopup(this); + } #endif addPlugin(plugin); Index: src/notifybymacosnotificationcenter.h =================================================================== --- /dev/null +++ src/notifybymacosnotificationcenter.h @@ -0,0 +1,20 @@ +#ifndef NOTIFYBYMACOSNOTIFICATIONCENTER_H +#define NOTIFYBYMACOSNOTIFICATIONCENTER_H + +#include "knotificationplugin.h" + +class NotifyByMacOSNotificationCenter : public KNotificationPlugin +{ + Q_OBJECT + +public: + NotifyByMacOSNotificationCenter(QObject* parent); + ~NotifyByMacOSNotificationCenter() override; + + QString optionName() override { return QStringLiteral("Popup"); } + void notify(KNotification *notification, KNotifyConfig *config) override; + void update(KNotification *notification, KNotifyConfig *config) override; + void close(KNotification *notification) override; +}; + +#endif // NOTIFYBYMACOSNOTIFICATIONCENTER_H Index: src/notifybymacosnotificationcenter.mm =================================================================== --- /dev/null +++ src/notifybymacosnotificationcenter.mm @@ -0,0 +1,218 @@ +#include "notifybymacosnotificationcenter.h" +#include "knotification.h" +#include "knotifyconfig.h" +#include "debug_p.h" + +#include +#include +#include + +#import +#import + +Q_FORWARD_DECLARE_OBJC_CLASS(MacOSNotificationCenterDelegate); + +class MacOSNotificationCenterPrivate +{ +public: + static MacOSNotificationCenterPrivate *instance(); + ~MacOSNotificationCenterPrivate(); + + QMap m_notifications; + + int m_internalCounter; +private: + MacOSNotificationCenterPrivate(); + MacOSNotificationCenterDelegate *m_delegate; + static MacOSNotificationCenterPrivate *m_instance; +}; + +@interface MacOSNotificationCenterDelegate : NSObject {} +@end + +@implementation MacOSNotificationCenterDelegate +- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification +{ + Q_UNUSED(center); + Q_UNUSED(notification); + return YES; +} + +- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification +{ + Q_UNUSED(center); + Q_UNUSED(notification); + qCDebug(LOG_KNOTIFICATIONS) << "Send notification " << [notification.userInfo[@"id"] intValue]; +} + +- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification +{ + Q_UNUSED(center); + qCDebug(LOG_KNOTIFICATIONS) << "User clicked on notification " + << [notification.userInfo[@"id"] intValue] + << ", internal Id: " + << [notification.userInfo[@"internalId"] intValue]; + + switch (notification.activationType) { + case NSUserNotificationActivationTypeReplied: + qCDebug(LOG_KNOTIFICATIONS) << "Replied clicked"; + break; + case NSUserNotificationActivationTypeContentsClicked: { + qCDebug(LOG_KNOTIFICATIONS) << "Content clicked"; + KNotification *originNotification = MacOSNotificationCenterPrivate::instance()->m_notifications.value([notification.userInfo[@"internalId"] intValue]); + if (!originNotification || originNotification->defaultAction().isNull()) { + break; + } + emit originNotification->activate(); + } + break; + case NSUserNotificationActivationTypeActionButtonClicked: { + qCDebug(LOG_KNOTIFICATIONS) << "Main action clicked"; + KNotification *originNotification = MacOSNotificationCenterPrivate::instance()->m_notifications.value([notification.userInfo[@"internalId"] intValue]); + if (!originNotification) { + break; + } + emit originNotification->activate(1); + } + break; + case NSUserNotificationActivationTypeAdditionalActionClicked: { + qCDebug(LOG_KNOTIFICATIONS) << "Additional action clicked"; + KNotification *originNotification = MacOSNotificationCenterPrivate::instance()->m_notifications.value([notification.userInfo[@"internalId"] intValue]); + if (!originNotification) { + break; + } + emit originNotification->activate([notification.additionalActivationAction.identifier intValue] + 1); + } + break; + default: + qCDebug(LOG_KNOTIFICATIONS) << "Other clicked"; + break; + } +} +@end + +MacOSNotificationCenterPrivate *MacOSNotificationCenterPrivate::m_instance = nullptr; + +MacOSNotificationCenterPrivate::MacOSNotificationCenterPrivate() + : m_internalCounter(0) +{ + // Set delegate + m_delegate = [MacOSNotificationCenterDelegate alloc]; + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:m_delegate]; +} + +MacOSNotificationCenterPrivate::~MacOSNotificationCenterPrivate() +{ + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate: nil]; + [m_delegate release]; + + // Try to finish all KNotification + for (auto it = m_notifications.constBegin(); it != m_notifications.constEnd(); it++) { + it.value()->deref(); + } + + // Try to finish all NSNotification + NSArray *deliveredNotifications = [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications; + for (NSUserNotification *deliveredNotification in deliveredNotifications) { + // Remove NSNotification in notification center + [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification]; + } +} + +MacOSNotificationCenterPrivate *MacOSNotificationCenterPrivate::instance() { + if (!m_instance) { + m_instance = new MacOSNotificationCenterPrivate(); + } + return m_instance; +} + + +NotifyByMacOSNotificationCenter::NotifyByMacOSNotificationCenter(QObject* parent) + : KNotificationPlugin(parent) +{ + // Clear notifications + NSArray *deliveredNotifications = [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications; + for (NSUserNotification *deliveredNotification in deliveredNotifications) { + // Remove NSNotification in notification center + [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification]; + } +} + +NotifyByMacOSNotificationCenter::~NotifyByMacOSNotificationCenter() +{ +} + +void NotifyByMacOSNotificationCenter::notify(KNotification *notification, KNotifyConfig *config) +{ + Q_UNUSED(config); + + int internalId = MacOSNotificationCenterPrivate::instance()->m_internalCounter++; + NSUserNotification *osxNotification = [[[NSUserNotification alloc] init] autorelease]; + NSString *notificationId = [NSString stringWithFormat: @"%d", notification->id()]; + NSString *internalNotificationId = [NSString stringWithFormat: @"%d", internalId]; + NSString *title = notification->title().toNSString(); + NSString *text = notification->text().toNSString(); + + osxNotification.title = title; + osxNotification.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: notificationId, @"id", + internalNotificationId, @"internalId", nil]; + osxNotification.informativeText = text; + osxNotification.contentImage = QtMac::toNSImage(notification->pixmap()); + + if (notification->actions().isEmpty()) { + // Remove all buttons + osxNotification.hasReplyButton = false; + osxNotification.hasActionButton = false; + } else { + osxNotification.hasActionButton = true; + // Workaround: this "API" will cause refuse from Apple + // [osxNotification setValue:[NSNumber numberWithBool:YES] forKey: @"_alwaysShowAlternateActionMenu"]; + + // Assign first action to action button + if (notification->actions().length() > 0) { + osxNotification.actionButtonTitle = notification->actions().at(0).toNSString(); + } + + // Construct a list for all actions left for additional buttons + NSMutableArray *actions = [[NSMutableArray alloc] init]; + for (int index = 1; index < notification->actions().length(); index++) { + NSUserNotificationAction *action = + [NSUserNotificationAction actionWithIdentifier: [NSString stringWithFormat:@"%d", index] + title: notification->actions().at(index).toNSString()]; + [actions addObject: action]; + } + osxNotification.additionalActions = actions; + } + + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: osxNotification]; + + MacOSNotificationCenterPrivate::instance()->m_notifications.insert(internalId, notification); +} + +void NotifyByMacOSNotificationCenter::close(KNotification *notification) +{ + qCDebug(LOG_KNOTIFICATIONS) << "Remove notification " << notification->id(); + + NSArray *deliveredNotifications = [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications; + for (NSUserNotification *deliveredNotification in deliveredNotifications) { + if ([deliveredNotification.userInfo[@"id"] intValue] == notification->id()) { + // Remove KNotification in mapping + int internalId = [deliveredNotification.userInfo[@"id"] intValue]; + + MacOSNotificationCenterPrivate::instance()->m_notifications.remove(internalId); + + // Remove NSNotification in notification center + [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification]; + finish(notification); + return; + } + } + qCDebug(LOG_KNOTIFICATIONS) << "Notification " << notification->id() << " not found"; + finish(notification); +} + +void NotifyByMacOSNotificationCenter::update(KNotification *notification, KNotifyConfig *config) +{ + close(notification); + notify(notification, config); +}