| /* |
| * Copyright © 2015 Patrick Griffis |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * 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.1 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", 200)); |
| |
| 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; |
| } |
| } |
| |
| [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; |
| } |