/* * 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 . * * Authors: Patrick Griffis */ #include "config.h" #import #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); } } G_GNUC_BEGIN_IGNORE_DEPRECATIONS /* first deprecated in macOS 11.0 - All NSUserNotifications API should be * replaced with UserNotifications.frameworks API */ @interface GNotificationCenterDelegate : NSObject @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]; } G_GNUC_END_IGNORE_DEPRECATIONS 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; }