diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/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) diff --git a/src/knotificationmanager.cpp b/src/knotificationmanager.cpp --- a/src/knotificationmanager.cpp +++ b/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); diff --git a/src/notifybymacosnotificationcenter.h b/src/notifybymacosnotificationcenter.h new file mode 100644 --- /dev/null +++ b/src/notifybymacosnotificationcenter.h @@ -0,0 +1,22 @@ +#ifndef NOTIFYBYMACOSNOTIFICATIONCENTER_H +#define NOTIFYBYMACOSNOTIFICATIONCENTER_H + +#include "knotificationplugin.h" + +class MacOSNotificationCenterPrivate; + +class NotifyByMacOSNotificationCenter : public KNotificationPlugin +{ + Q_OBJECT + +public: + NotifyByMacOSNotificationCenter(QObject* parent); + ~NotifyByMacOSNotificationCenter(); + + 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 diff --git a/src/notifybymacosnotificationcenter.mm b/src/notifybymacosnotificationcenter.mm new file mode 100644 --- /dev/null +++ b/src/notifybymacosnotificationcenter.mm @@ -0,0 +1,223 @@ +#include "notifybymacosnotificationcenter.h" +#include "knotification.h" +#include "knotifyconfig.h" +#include "debug_p.h" + +#include +#include +#include +#include + +#include +#import + +class MacOSNotificationCenterPrivate +{ +public: + MacOSNotificationCenterPrivate(); + ~MacOSNotificationCenterPrivate(); + + void insertKNotification(int internalId, KNotification *notification); + KNotification *getKNotification(int internalId); + const KNotification *getKNotification(int internalId) const; + KNotification *takeKNotification(int internalId); + void removeKNotification(int internalId); + + int generateInternalId() { return m_internalCounter++; } +private: + id m_delegate; + + int m_internalCounter; + QMap m_notifications; +}; + +static MacOSNotificationCenterPrivate macosNotificationCenterPrivate; + +@interface MacOSNotificationCenterDelegate : NSObject {} +@end + +@implementation MacOSNotificationCenterDelegate +- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification +{ + return YES; +} + +- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification +{ + qCDebug(LOG_KNOTIFICATIONS) << "Send notification " << [notification.userInfo[@"id"] intValue]; +} + +- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification +{ + KNotification *originNotification; + 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) << "Contents clicked"; + break; + case NSUserNotificationActivationTypeActionButtonClicked: + qCDebug(LOG_KNOTIFICATIONS) << "ActionButton clicked"; + break; + case NSUserNotificationActivationTypeAdditionalActionClicked: + qCDebug(LOG_KNOTIFICATIONS) << "AdditionalAction clicked"; + originNotification = macosNotificationCenterPrivate.getKNotification([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() +{ + // Set delegate + m_delegate = [MacOSNotificationCenterDelegate alloc]; + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:m_delegate]; + + // Init internal notification counter + m_internalCounter = 0; +} + +MacOSNotificationCenterPrivate::~MacOSNotificationCenterPrivate() +{ + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate: nil]; + [m_delegate release]; + + // Try to finish all KNotification + for (KNotification *notification : m_notifications.values()) { + notification->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]; + } +} + +void MacOSNotificationCenterPrivate::insertKNotification(int internalId, KNotification *notification) +{ + if (!notification) return; + + m_notifications.insert(internalId, notification); +} + +KNotification *MacOSNotificationCenterPrivate::getKNotification(int internalId) +{ + return m_notifications[internalId]; +} + +const KNotification *MacOSNotificationCenterPrivate::getKNotification(int internalId) const +{ + return m_notifications[internalId]; +} + +KNotification *MacOSNotificationCenterPrivate::takeKNotification(int internalId) +{ + return m_notifications.take(internalId); +} + +void MacOSNotificationCenterPrivate::removeKNotification(int internalId) +{ + m_notifications.remove(internalId); +} + +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]; + } + + qCDebug(LOG_KNOTIFICATIONS) << "Knotification macos backend created"; +} + +NotifyByMacOSNotificationCenter::~NotifyByMacOSNotificationCenter() +{ + qCDebug(LOG_KNOTIFICATIONS) << "Knotification macos backend deleted"; +} + +void NotifyByMacOSNotificationCenter::notify(KNotification *notification, KNotifyConfig *config) +{ + qCDebug(LOG_KNOTIFICATIONS) << "Test notification " << notification->id(); + + int internalId = macosNotificationCenterPrivate.generateInternalId(); + NSUserNotification *osxNotification = [[[NSUserNotification alloc] init] autorelease]; + NSString *notificationId = [NSString stringWithFormat: @"%d", notification->id()], + *internalNotificationId = [NSString stringWithFormat: @"%d", internalId]; + + CFStringRef cfTitle = notification->title().toCFString(), + cfText = notification->text().toCFString(); + + osxNotification.title = [NSString stringWithString: (NSString *)cfTitle]; + osxNotification.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: notificationId, @"id", + internalNotificationId, @"internalId", nil]; + osxNotification.informativeText = [NSString stringWithString: (NSString *)cfText]; + 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"]; + + // Construct a list for all actions + NSMutableArray *actions = [[NSMutableArray alloc] init]; + for (int index = 0; 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.insertKNotification(internalId, notification); + + CFRelease(cfTitle); + CFRelease(cfText); +} + +void NotifyByMacOSNotificationCenter::close(KNotification *notification) +{ + qCDebug(LOG_KNOTIFICATIONS) << "Test 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.removeKNotification(internalId); + + // Remove NSNotification in notification center + [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification]; + finish(notification); + return; + } + } + qCDebug(LOG_KNOTIFICATIONS) << "Notification " << notification->id() << " not found"; +} + +void NotifyByMacOSNotificationCenter::update(KNotification *notification, KNotifyConfig *config) +{ + close(notification); + notify(notification, config); +}