1/* 2 * Copyright © 2015 Patrick Griffis 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General 15 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. 16 * 17 * Authors: Patrick Griffis 18 */ 19 20#include "config.h" 21 22#import <Cocoa/Cocoa.h> 23#include "gnotificationbackend.h" 24#include "gapplication.h" 25#include "gaction.h" 26#include "gactiongroup.h" 27#include "giomodule-priv.h" 28#include "gnotification-private.h" 29#include "gthemedicon.h" 30#include "gfileicon.h" 31#include "gfile.h" 32 33#define G_TYPE_COCOA_NOTIFICATION_BACKEND (g_cocoa_notification_backend_get_type ()) 34#define G_COCOA_NOTIFICATION_BACKEND(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_COCOA_NOTIFICATION_BACKEND, GCocoaNotificationBackend)) 35 36typedef struct _GCocoaNotificationBackend GCocoaNotificationBackend; 37typedef GNotificationBackendClass GCocoaNotificationBackendClass; 38struct _GCocoaNotificationBackend 39{ 40 GNotificationBackend parent; 41}; 42 43GType g_cocoa_notification_backend_get_type (void); 44 45G_DEFINE_TYPE_WITH_CODE (GCocoaNotificationBackend, g_cocoa_notification_backend, G_TYPE_NOTIFICATION_BACKEND, 46 _g_io_modules_ensure_extension_points_registered (); 47 g_io_extension_point_implement (G_NOTIFICATION_BACKEND_EXTENSION_POINT_NAME, g_define_type_id, "cocoa", 200)); 48 49static NSString * 50nsstring_from_cstr (const char *cstr) 51{ 52 if (!cstr) 53 return nil; 54 55 return [[NSString alloc] initWithUTF8String:cstr]; 56} 57 58static NSImage* 59nsimage_from_gicon (GIcon *icon) 60{ 61 if (G_IS_FILE_ICON (icon)) 62 { 63 NSImage *image = nil; 64 GFile *file; 65 char *path; 66 67 file = g_file_icon_get_file (G_FILE_ICON (icon)); 68 path = g_file_get_path (file); 69 if (path) 70 { 71 NSString *str_path = nsstring_from_cstr (path); 72 image = [[NSImage alloc] initByReferencingFile:str_path]; 73 74 [str_path release]; 75 g_free (path); 76 } 77 return image; 78 } 79 else 80 { 81 g_warning ("This icon type is not handled by this NotificationBackend"); 82 return nil; 83 } 84} 85 86static void 87activate_detailed_action (const char * action) 88{ 89 char *name; 90 GVariant *target; 91 92 if (!g_str_has_prefix (action, "app.")) 93 { 94 g_warning ("Notification action does not have \"app.\" prefix"); 95 return; 96 } 97 98 if (g_action_parse_detailed_name (action, &name, &target, NULL)) 99 { 100 g_action_group_activate_action (G_ACTION_GROUP (g_application_get_default()), name + 4, target); 101 g_free (name); 102 if (target) 103 g_variant_unref (target); 104 } 105} 106 107@interface GNotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate> @end 108@implementation GNotificationCenterDelegate 109 110-(void) userNotificationCenter:(NSUserNotificationCenter*) center 111 didActivateNotification:(NSUserNotification*) notification 112{ 113 if ([notification activationType] == NSUserNotificationActivationTypeContentsClicked) 114 { 115 const char *action = [[notification userInfo][@"default"] UTF8String]; 116 if (action) 117 activate_detailed_action (action); 118 /* OSX Always activates the front window */ 119 } 120 else if ([notification activationType] == NSUserNotificationActivationTypeActionButtonClicked) 121 { 122 const char *action = [[notification userInfo][@"button0"] UTF8String]; 123 if (action) 124 activate_detailed_action (action); 125 } 126 127 [center removeDeliveredNotification:notification]; 128} 129 130@end 131 132static GNotificationCenterDelegate *cocoa_notification_delegate; 133 134static gboolean 135g_cocoa_notification_backend_is_supported (void) 136{ 137 NSBundle *bundle = [NSBundle mainBundle]; 138 139 /* This is always actually supported, but without a bundle it does nothing */ 140 if (![bundle bundleIdentifier]) 141 return FALSE; 142 143 return TRUE; 144} 145 146static void 147add_actions_to_notification (NSUserNotification *userNotification, 148 GNotification *notification) 149{ 150 guint n_buttons = g_notification_get_n_buttons (notification); 151 char *action = NULL, *label = NULL; 152 GVariant *target = NULL; 153 NSMutableDictionary *user_info = nil; 154 155 if (g_notification_get_default_action (notification, &action, &target)) 156 { 157 char *detailed_name = g_action_print_detailed_name (action, target); 158 NSString *action_name = nsstring_from_cstr (detailed_name); 159 user_info = [[NSMutableDictionary alloc] init]; 160 161 user_info[@"default"] = action_name; 162 163 [action_name release]; 164 g_free (detailed_name); 165 g_clear_pointer (&action, g_free); 166 g_clear_pointer (&target, g_variant_unref); 167 } 168 169 if (n_buttons) 170 { 171 g_notification_get_button (notification, 0, &label, &action, &target); 172 if (label) 173 { 174 NSString *str_label = nsstring_from_cstr (label); 175 char *detailed_name = g_action_print_detailed_name (action, target); 176 NSString *action_name = nsstring_from_cstr (detailed_name); 177 178 if (!user_info) 179 user_info = [[NSMutableDictionary alloc] init]; 180 181 user_info[@"button0"] = action_name; 182 userNotification.actionButtonTitle = str_label; 183 184 [str_label release]; 185 [action_name release]; 186 g_free (label); 187 g_free (action); 188 g_free (detailed_name); 189 g_clear_pointer (&target, g_variant_unref); 190 } 191 192 if (n_buttons > 1) 193 g_warning ("Only a single button is currently supported by this NotificationBackend"); 194 } 195 196 userNotification.userInfo = user_info; 197 [user_info release]; 198} 199 200static void 201g_cocoa_notification_backend_send_notification (GNotificationBackend *backend, 202 const gchar *cstr_id, 203 GNotification *notification) 204{ 205 NSString *str_title = nil, *str_text = nil, *str_id = nil; 206 NSImage *content = nil; 207 const char *cstr; 208 GIcon *icon; 209 NSUserNotification *userNotification; 210 NSUserNotificationCenter *center; 211 212 if ((cstr = g_notification_get_title (notification))) 213 str_title = nsstring_from_cstr (cstr); 214 if ((cstr = g_notification_get_body (notification))) 215 str_text = nsstring_from_cstr (cstr); 216 if (cstr_id != NULL) 217 str_id = nsstring_from_cstr (cstr_id); 218 if ((icon = g_notification_get_icon (notification))) 219 content = nsimage_from_gicon (icon); 220 /* NOTE: There is no priority */ 221 222 userNotification = [NSUserNotification new]; 223 userNotification.title = str_title; 224 userNotification.informativeText = str_text; 225 userNotification.identifier = str_id; 226 userNotification.contentImage = content; 227 /* NOTE: Buttons only show up if your bundle has NSUserNotificationAlertStyle set to "alerts" */ 228 add_actions_to_notification (userNotification, notification); 229 230 if (!cocoa_notification_delegate) 231 cocoa_notification_delegate = [[GNotificationCenterDelegate alloc] init]; 232 233 center = [NSUserNotificationCenter defaultUserNotificationCenter]; 234 center.delegate = cocoa_notification_delegate; 235 [center deliverNotification:userNotification]; 236 237 [str_title release]; 238 [str_text release]; 239 [str_id release]; 240 [content release]; 241 [userNotification release]; 242} 243 244static void 245g_cocoa_notification_backend_withdraw_notification (GNotificationBackend *backend, 246 const gchar *cstr_id) 247{ 248 NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; 249 NSArray *notifications = [center deliveredNotifications]; 250 NSString *str_id = nsstring_from_cstr (cstr_id); 251 252 for (NSUserNotification *notification in notifications) 253 { 254 if ([notification.identifier compare:str_id] == NSOrderedSame) 255 { 256 [center removeDeliveredNotification:notification]; 257 break; 258 } 259 } 260 261 [str_id release]; 262} 263 264static void 265g_cocoa_notification_backend_init (GCocoaNotificationBackend *backend) 266{ 267} 268 269static void 270g_cocoa_notification_backend_class_init (GCocoaNotificationBackendClass *klass) 271{ 272 GNotificationBackendClass *backend_class = G_NOTIFICATION_BACKEND_CLASS (klass); 273 274 backend_class->is_supported = g_cocoa_notification_backend_is_supported; 275 backend_class->send_notification = g_cocoa_notification_backend_send_notification; 276 backend_class->withdraw_notification = g_cocoa_notification_backend_withdraw_notification; 277} 278