From be6aa6e81b44be67518731d67bb2b72268d351e8 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Thu, 3 Sep 2015 18:01:14 +0200
Subject: Initial support for OS X Notification Center

This implements basic support for OS X Notification Center
notifications with simple banner notifications showing type,
contact and message/presence information. A click on the
notification opens the chat view to the contact.

The Notification Center backend will be used on OS X if the Growl
notification backend is not used.

This code requires OS X version 10.8 or later.

Test-Information:

Tested presence and message notifications between multiple Swift
accounts and instances on OS X 10.9.5.

Change-Id: I7b9e2132cab25e086e0912191562cad8f4cbae38

diff --git a/SwifTools/Notifier/NotificationCenterNotifier.h b/SwifTools/Notifier/NotificationCenterNotifier.h
new file mode 100644
index 0000000..0d43c5b
--- /dev/null
+++ b/SwifTools/Notifier/NotificationCenterNotifier.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <SwifTools/Notifier/Notifier.h>
+
+namespace Swift {
+
+/**
+ * @brief The NotificationCenterNotifier class implmenents the notification interface for the
+ *        OS X Notification Center API.
+ */
+class NotificationCenterNotifier : public Notifier {
+public:
+	NotificationCenterNotifier();
+	virtual ~NotificationCenterNotifier();
+
+	virtual void showMessage(Type type, const std::string& subject, const std::string& description, const boost::filesystem::path& picture, boost::function<void ()> callback);
+	virtual void purgeCallbacks();
+
+	/**
+	 * @brief The handleUserNotificationActivated is called by the delegate, when a user activates/clicks on a notification.
+	 * @param identifier The std::string UUID identifiying the notification.
+	 */
+	void handleUserNotificationActivated(const std::string& identifier);
+
+private:
+	class Private;
+	boost::shared_ptr<Private> p;
+};
+
+}
diff --git a/SwifTools/Notifier/NotificationCenterNotifier.mm b/SwifTools/Notifier/NotificationCenterNotifier.mm
new file mode 100644
index 0000000..df092ff
--- /dev/null
+++ b/SwifTools/Notifier/NotificationCenterNotifier.mm
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <SwifTools/Notifier/NotificationCenterNotifier.h>
+
+#include <map>
+#include <string>
+
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Base/Log.h>
+
+#import <Cocoa/Cocoa.h>
+
+#include <SwifTools/Notifier/NotificationCenterNotifierDelegate.h>
+#include <SwifTools/Cocoa/CocoaUtil.h>
+
+namespace {
+	struct Context {
+		Context(const boost::function<void()>& callback) : callback(new boost::function<void()>(callback)) {
+		}
+
+		~Context() {
+			delete callback;
+		}
+
+		boost::function<void()>* callback;
+	};
+}
+
+namespace Swift {
+
+class NotificationCenterNotifier::Private {
+	public:
+		std::map<std::string, boost::shared_ptr<Context> > callbacksForNotifications;
+		boost::intrusive_ptr<NotificationCenterNotifierDelegate> delegate;
+};
+
+NotificationCenterNotifier::NotificationCenterNotifier() {
+	p = boost::make_shared<Private>();
+	p->delegate = boost::intrusive_ptr<NotificationCenterNotifierDelegate>([[NotificationCenterNotifierDelegate alloc] init], false);
+	[p->delegate.get() setNotifier: this];
+
+	[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate: p->delegate.get()];
+}
+
+NotificationCenterNotifier::~NotificationCenterNotifier() {
+	[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate: nil];
+	p->callbacksForNotifications.clear();
+}
+
+void NotificationCenterNotifier::showMessage(Type type, const std::string& subject, const std::string& description, const boost::filesystem::path& picture, boost::function<void ()> callback) {
+	NSUserNotification* notification = [[NSUserNotification alloc] init];
+	notification.title = STD2NSSTRING(typeToString(type));
+	notification.subtitle = STD2NSSTRING(subject);
+	notification.informativeText = STD2NSSTRING(description);
+	notification.contentImage = [[NSImage alloc] initWithContentsOfFile: STD2NSSTRING(picture.string())];
+
+	// The OS X Notification Center API does not allow to attach custom data, like a pointer to a callback function,
+	// to the NSUserNotification object. Therefore we maintain a mapping from a NSUserNotification instance's identification
+	// to their respective callbacks.
+	notification.identifier = [[NSUUID UUID] UUIDString];
+
+	/// \todo Currently the elements are only removed on application exit. Ideally the notifications not required anymore
+	///       are removed from the map; e.g. when visiting a chat view, all notifications from that view can be removed from
+	///       the map and the NSUserNotificationCenter.
+	p->callbacksForNotifications[NS2STDSTRING(notification.identifier)] = boost::make_shared<Context>(callback);
+	[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
+}
+
+void NotificationCenterNotifier::purgeCallbacks() {
+	p->callbacksForNotifications.clear();
+}
+
+void NotificationCenterNotifier::handleUserNotificationActivated(const std::string& identifier) {
+	if (p->callbacksForNotifications.find(identifier) != p->callbacksForNotifications.end()) {
+		(*p->callbacksForNotifications[identifier]->callback)();
+	}
+	else {
+		SWIFT_LOG(warning) << "Missing callback entry for activated notification. The activate notification may come from another instance." << std::endl;
+	}
+}
+
+}
diff --git a/SwifTools/Notifier/NotificationCenterNotifierDelegate.h b/SwifTools/Notifier/NotificationCenterNotifierDelegate.h
new file mode 100644
index 0000000..ea8fae0
--- /dev/null
+++ b/SwifTools/Notifier/NotificationCenterNotifierDelegate.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#import <Cocoa/Cocoa.h>
+
+namespace Swift {
+	class NotificationCenterNotifier;
+}
+
+@interface NotificationCenterNotifierDelegate : NSObject<NSUserNotificationCenterDelegate> {
+}
+
+@property (nonatomic) Swift::NotificationCenterNotifier* notifier;
+
+- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification;
+
+@end
diff --git a/SwifTools/Notifier/NotificationCenterNotifierDelegate.mm b/SwifTools/Notifier/NotificationCenterNotifierDelegate.mm
new file mode 100644
index 0000000..617619c
--- /dev/null
+++ b/SwifTools/Notifier/NotificationCenterNotifierDelegate.mm
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2015 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#import "SwifTools/Notifier/NotificationCenterNotifierDelegate.h"
+
+#include <string>
+
+#include <SwifTools/Cocoa/CocoaUtil.h>
+#include <SwifTools/Notifier/NotificationCenterNotifier.h>
+
+@implementation NotificationCenterNotifierDelegate
+
+using namespace Swift;
+
+@synthesize notifier;
+
+- (void)userNotificationCenter:(NSUserNotificationCenter *) center didActivateNotification:(NSUserNotification *)notification {
+	(void)center;
+	std::string identifier = NS2STDSTRING(notification.identifier);
+	notifier->handleUserNotificationActivated(identifier);
+}
+
+@end
diff --git a/SwifTools/Notifier/SConscript b/SwifTools/Notifier/SConscript
index 98b5400..e60937b 100644
--- a/SwifTools/Notifier/SConscript
+++ b/SwifTools/Notifier/SConscript
@@ -11,6 +11,12 @@ if swiftools_env.get("HAVE_GROWL", False) :
 			"GrowlNotifier.mm",
 			"GrowlNotifierDelegate.mm",
 		]
+elif myenv["PLATFORM"] == "darwin" :
+	sources += [
+			"NotificationCenterNotifier.mm",
+			"NotificationCenterNotifierDelegate.mm",
+		]
+
 if swiftools_env.get("HAVE_SNARL", False) :
 	myenv.MergeFlags(myenv["SNARL_FLAGS"])
 	sources += [
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index 6d8ac7b..756f530 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -56,6 +56,8 @@
 #include <SwifTools/Notifier/GrowlNotifier.h>
 #elif defined(SWIFTEN_PLATFORM_LINUX)
 #include <Swift/QtUI/FreeDesktopNotifier.h>
+#elif defined(SWIFTEN_PLATFORM_MACOSX)
+#include <SwifTools/Notifier/NotificationCenterNotifier.h>
 #else
 #include <SwifTools/Notifier/NullNotifier.h>
 #endif
@@ -196,6 +198,8 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 	notifier_ = new WindowsNotifier(SWIFT_APPLICATION_NAME, applicationPathProvider_->getResourcePath("/images/logo-icon-32.png"), systemTray->getQSystemTrayIcon());
 #elif defined(SWIFTEN_PLATFORM_LINUX)
 	notifier_ = new FreeDesktopNotifier(SWIFT_APPLICATION_NAME);
+#elif defined(SWIFTEN_PLATFORM_MACOSX)
+	notifier_ = new NotificationCenterNotifier();
 #else
 	notifier_ = new NullNotifier();
 #endif
-- 
cgit v0.10.2-6-g49f6