[glib] Implement GNotification on OSX



commit 36e093a31a9eb12021e7780b9e322c29763ffa58
Author: Patrick Griffis <tingping tingping se>
Date:   Wed Mar 25 15:51:29 2015 -0400

    Implement GNotification on OSX
    
    https://bugzilla.gnome.org/show_bug.cgi?id=747146

 gio/Makefile.am                 |    5 +-
 gio/gcocoanotificationbackend.c |  278 +++++++++++++++++++++++++++++++++++++++
 gio/giomodule.c                 |    7 +
 gio/gnotificationbackend.c      |    4 +-
 4 files changed, 292 insertions(+), 2 deletions(-)
---
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 757f924..627c63e 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -265,6 +265,9 @@ unix_sources = \
        ggtknotificationbackend.c \
        $(NULL)
 
+if OS_COCOA
+unix_sources += gcocoanotificationbackend.c
+endif
 
 giounixincludedir=$(includedir)/gio-unix-2.0/gio
 giounixinclude_HEADERS = \
@@ -522,7 +525,7 @@ libgio_2_0_la_LDFLAGS = $(GLIB_LINK_FLAGS) \
 if OS_COCOA
 # This is dumb.  The ObjC source file should be properly named .m
 libgio_2_0_la_CFLAGS += -xobjective-c
-libgio_2_0_la_LDFLAGS += -Wl,-framework,Foundation
+libgio_2_0_la_LDFLAGS += -Wl,-framework,Foundation -Wl,-framework,AppKit
 endif
 
 libgio_2_0_la_DEPENDENCIES = $(gio_win32_res) $(gio_def) $(platform_deps)
diff --git a/gio/gcocoanotificationbackend.c b/gio/gcocoanotificationbackend.c
new file mode 100644
index 0000000..8a78c99
--- /dev/null
+++ b/gio/gcocoanotificationbackend.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright © 2015 Patrick Griffis
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Patrick Griffis
+ */
+
+#include "config.h"
+
+#import <Cocoa/Cocoa.h>
+#include "gnotificationbackend.h"
+#include "gapplication.h"
+#include "gaction.h"
+#include "gactiongroup.h"
+#include "giomodule-priv.h"
+#include "gnotification-private.h"
+#include "gthemedicon.h"
+#include "gfileicon.h"
+#include "gfile.h"
+
+#define G_TYPE_COCOA_NOTIFICATION_BACKEND  (g_cocoa_notification_backend_get_type ())
+#define G_COCOA_NOTIFICATION_BACKEND(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), 
G_TYPE_COCOA_NOTIFICATION_BACKEND, GCocoaNotificationBackend))
+
+typedef struct _GCocoaNotificationBackend GCocoaNotificationBackend;
+typedef GNotificationBackendClass            GCocoaNotificationBackendClass;
+struct _GCocoaNotificationBackend
+{
+  GNotificationBackend parent;
+};
+
+GType g_cocoa_notification_backend_get_type (void);
+
+G_DEFINE_TYPE_WITH_CODE (GCocoaNotificationBackend, g_cocoa_notification_backend, 
G_TYPE_NOTIFICATION_BACKEND,
+  _g_io_modules_ensure_extension_points_registered ();
+  g_io_extension_point_implement (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME, g_define_type_id, "cocoa", 
0));
+
+static NSString *
+nsstring_from_cstr (const char *cstr)
+{
+  if (!cstr)
+    return nil;
+
+  return [[NSString alloc] initWithUTF8String:cstr];
+}
+
+static NSImage*
+nsimage_from_gicon (GIcon *icon)
+{
+  if (G_IS_FILE_ICON (icon))
+    {
+      NSImage *image = nil;
+      GFile *file;
+      char *path;
+
+      file = g_file_icon_get_file (G_FILE_ICON (icon));
+      path = g_file_get_path (file);
+      if (path)
+        {
+          NSString *str_path = nsstring_from_cstr (path);
+          image = [[NSImage alloc] initByReferencingFile:str_path];
+
+          [str_path release];
+          g_free (path);
+        }
+      return image;
+    }
+  else
+    {
+      g_warning ("This icon type is not handled by this NotificationBackend");
+      return nil;
+    }
+}
+
+static void
+activate_detailed_action (const char * action)
+{
+  char *name;
+  GVariant *target;
+
+  if (!g_str_has_prefix (action, "app."))
+    {
+      g_warning ("Notification action does not have \"app.\" prefix");
+      return;
+    }
+
+  if (g_action_parse_detailed_name (action, &name, &target, NULL))
+    {
+      g_action_group_activate_action (G_ACTION_GROUP (g_application_get_default()), name + 4, target);
+      g_free (name);
+      if (target)
+        g_variant_unref (target);
+    }
+}
+
+ interface GNotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate> @end
+ implementation GNotificationCenterDelegate
+
+-(void) userNotificationCenter:(NSUserNotificationCenter*) center
+       didActivateNotification:(NSUserNotification*)       notification
+{
+  if ([notification activationType] == NSUserNotificationActivationTypeContentsClicked)
+    {
+      const char *action = [[notification userInfo][@"default"] UTF8String];
+      if (action)
+        activate_detailed_action (action);
+      /* OSX Always activates the front window */
+    }
+  else if ([notification activationType] == NSUserNotificationActivationTypeActionButtonClicked)
+    {
+      const char *action = [[notification userInfo][@"button0"] UTF8String];
+      if (action)
+        activate_detailed_action (action);
+    }
+
+  [center removeDeliveredNotification:notification];
+}
+
+ end
+
+static GNotificationCenterDelegate *cocoa_notification_delegate;
+
+static gboolean
+g_cocoa_notification_backend_is_supported (void)
+{
+  NSBundle *bundle = [NSBundle mainBundle];
+
+  /* This is always actually supported, but without a bundle it does nothing */
+  if (![bundle bundleIdentifier])
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+add_actions_to_notification (NSUserNotification   *userNotification,
+                             GNotification        *notification)
+{
+  guint n_buttons = g_notification_get_n_buttons (notification);
+  char *action = NULL, *label = NULL;
+  GVariant *target = NULL;
+  NSMutableDictionary *user_info = nil;
+
+  if (g_notification_get_default_action (notification, &action, &target))
+    {
+      char *detailed_name = g_action_print_detailed_name (action, target);
+      NSString *action_name = nsstring_from_cstr (detailed_name);
+      user_info = [[NSMutableDictionary alloc] init];
+
+      user_info[@"default"] = action_name;
+
+      [action_name release];
+      g_free (detailed_name);
+      g_clear_pointer (&action, g_free);
+      g_clear_pointer (&target, g_variant_unref);
+    }
+
+  if (n_buttons)
+    {
+      g_notification_get_button (notification, 0, &label, &action, &target);
+      if (label)
+        {
+          NSString *str_label = nsstring_from_cstr (label);
+          char *detailed_name = g_action_print_detailed_name (action, target);
+          NSString *action_name = nsstring_from_cstr (detailed_name);
+
+          if (!user_info)
+            user_info = [[NSMutableDictionary alloc] init];
+
+          user_info[@"button0"] = action_name;
+          userNotification.actionButtonTitle = str_label;
+
+          [str_label release];
+          [action_name release];
+          g_free (label);
+          g_free (action);
+          g_free (detailed_name);
+          g_clear_pointer (&target, g_variant_unref);
+        }
+
+      if (n_buttons > 1)
+        g_warning ("Only a single button is currently supported by this NotificationBackend");
+    }
+
+    userNotification.userInfo = user_info;
+    [user_info release];
+}
+
+static void
+g_cocoa_notification_backend_send_notification (GNotificationBackend *backend,
+                                                const gchar          *cstr_id,
+                                                GNotification        *notification)
+{
+  NSString *str_title = nil, *str_text = nil, *str_id = nil;
+  NSImage *content = nil;
+  const char *cstr;
+  GIcon *icon;
+  NSUserNotification *userNotification;
+  NSUserNotificationCenter *center;
+
+  if ((cstr = g_notification_get_title (notification)))
+    str_title = nsstring_from_cstr (cstr);
+  if ((cstr = g_notification_get_body (notification)))
+    str_text = nsstring_from_cstr (cstr);
+  if (cstr_id != NULL)
+    str_id = nsstring_from_cstr (cstr_id);
+  if ((icon = g_notification_get_icon (notification)))
+    content = nsimage_from_gicon (icon);
+  /* NOTE: There is no priority */
+
+  userNotification = [NSUserNotification new];
+  userNotification.title = str_title;
+  userNotification.informativeText = str_text;
+  userNotification.identifier = str_id;
+  userNotification.contentImage = content;
+  /* NOTE: Buttons only show up if your bundle has NSUserNotificationAlertStyle set to "alerts" */
+  add_actions_to_notification (userNotification, notification);
+
+  if (!cocoa_notification_delegate)
+    cocoa_notification_delegate = [[GNotificationCenterDelegate alloc] init];
+
+  center = [NSUserNotificationCenter defaultUserNotificationCenter];
+  center.delegate = cocoa_notification_delegate;
+  [center deliverNotification:userNotification];
+
+  [str_title release];
+  [str_text release];
+  [str_id release];
+  [content release];
+  [userNotification release];
+}
+
+static void
+g_cocoa_notification_backend_withdraw_notification (GNotificationBackend *backend,
+                                                    const gchar          *cstr_id)
+{
+  NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
+  NSArray *notifications = [center deliveredNotifications];
+  NSString *str_id = nsstring_from_cstr (cstr_id);
+
+  for (NSUserNotification *notification in notifications)
+    {
+      if ([notification.identifier compare:str_id] == NSOrderedSame)
+        {
+          [center removeDeliveredNotification:notification];
+          break;
+        }
+    }
+
+  [notifications release];
+  [str_id release];
+}
+
+static void
+g_cocoa_notification_backend_init (GCocoaNotificationBackend *backend)
+{
+}
+
+static void
+g_cocoa_notification_backend_class_init (GCocoaNotificationBackendClass *klass)
+{
+  GNotificationBackendClass *backend_class = G_NOTIFICATION_BACKEND_CLASS (klass);
+
+  backend_class->is_supported = g_cocoa_notification_backend_is_supported;
+  backend_class->send_notification = g_cocoa_notification_backend_send_notification;
+  backend_class->withdraw_notification = g_cocoa_notification_backend_withdraw_notification;
+}
diff --git a/gio/giomodule.c b/gio/giomodule.c
index f559c74..11581b4 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -911,6 +911,10 @@ extern GType g_fdo_notification_backend_get_type (void);
 extern GType g_gtk_notification_backend_get_type (void);
 #endif
 
+#ifdef HAVE_COCOA
+extern GType g_cocoa_notification_backend_get_type (void);
+#endif
+
 #ifdef G_PLATFORM_WIN32
 
 #include <windows.h>
@@ -1084,6 +1088,9 @@ _g_io_modules_ensure_loaded (void)
       g_type_ensure (g_fdo_notification_backend_get_type ());
       g_type_ensure (g_gtk_notification_backend_get_type ());
 #endif
+#ifdef HAVE_COCOA
+      g_type_ensure (g_cocoa_notification_backend_get_type ());
+#endif
 #ifdef G_OS_WIN32
       g_type_ensure (_g_winhttp_vfs_get_type ());
 #endif
diff --git a/gio/gnotificationbackend.c b/gio/gnotificationbackend.c
index c1e009d..c5d5b5b 100644
--- a/gio/gnotificationbackend.c
+++ b/gio/gnotificationbackend.c
@@ -55,7 +55,9 @@ g_notification_backend_new_default (GApplication *application)
    */
   backend->application = application;
 
-  backend->dbus_connection = g_object_ref (g_application_get_dbus_connection (application));
+  backend->dbus_connection = g_application_get_dbus_connection (application);
+  if (backend->dbus_connection)
+    g_object_ref (backend->dbus_connection);
 
   return backend;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]