summaryrefslogtreecommitdiffstats
path: root/macosx/tkMacOSXSysTray.c
diff options
context:
space:
mode:
Diffstat (limited to 'macosx/tkMacOSXSysTray.c')
-rw-r--r--macosx/tkMacOSXSysTray.c869
1 files changed, 869 insertions, 0 deletions
diff --git a/macosx/tkMacOSXSysTray.c b/macosx/tkMacOSXSysTray.c
new file mode 100644
index 0000000..a0f0829
--- /dev/null
+++ b/macosx/tkMacOSXSysTray.c
@@ -0,0 +1,869 @@
+/*
+ * tkMacOSXSysTray.c --
+ *
+ * tkMacOSXSysTray.c implements a "systray" Tcl command which allows
+ * one to change the system tray/taskbar icon of a Tk toplevel
+ * window and a "sysnotify" command to post system notifications.
+ * In macOS the icon appears on the right hand side of the menu bar.
+ *
+ * Copyright © 2020 Kevin Walzer/WordTech Communications LLC.
+ * Copyright © 2020 Jan Nijtmans.
+ * Copyright © 2020 Marc Culler.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include <tkInt.h>
+#include <tkMacOSXInt.h>
+#include "tkMacOSXPrivate.h"
+
+/*
+ * Prior to macOS 10.14 user notifications were handled by the NSApplication's
+ * NSUserNotificationCenter via a NSUserNotificationCenterDelegate object.
+ * These classes were defined in the CoreFoundation framework. In macOS 10.14
+ * a separate UserNotifications framework was introduced which adds some
+ * additional features, including custom controls on the notification window
+ * but primarily a requirement that an application must be authorized before
+ * being allowed to post a notification. This framework uses a different
+ * class, the UNUserNotificationCenter, and its delegate follows a different
+ * protocol, named UNUserNotificationCenterDelegate.
+ *
+ * In macOS 11.0 the NSUserNotificationCenter and its delegate protocol were
+ * deprecated. To make matters more complicated, it turns out that there is a
+ * secret undocumented additional requirement that an app which is not signed
+ * can never be authorized to send notifications via the UNNotificationCenter.
+ * (As of 11.0, it appears that it is sufficient to sign the app with a
+ * self-signed certificate, however.)
+ *
+ * The workaround implemented here is to define two classes, TkNSNotifier and
+ * TkUNNotifier, each of which provides one of these protocols on macOS 10.14
+ * and newer. If the TkUSNotifier is able to obtain authorization it is used.
+ * Otherwise, TkNSNotifier is used. Building TkNSNotifier on 11.0 or later
+ * produces deprecation warnings which are suppressed by enclosing the
+ * interface and implementation in #pragma blocks. The first time that the tk
+ * systray command in initialized in an interpreter an attempt is made to
+ * obtain authorization for sending notifications with the UNNotificationCenter
+ * on systems and the result is saved in a static variable.
+ */
+
+//#define DEBUG
+#ifdef DEBUG
+
+/*
+ * This macro uses the do ... while(0) trick to swallow semicolons. It logs to
+ * a temp file because apps launched from an icon have no stdout or stderr and
+ * because NSLog has a tendency to not produce any console messages at certain
+ * stages of launching an app.
+ */
+
+#define DEBUG_LOG(format, ...) \
+ do { \
+ FILE* logfile = fopen("/tmp/tklog", "a"); \
+ fprintf(logfile, format, ##__VA_ARGS__); \
+ fflush(logfile); \
+ fclose(logfile); } while (0)
+#else
+#define DEBUG_LOG(format, ...)
+#endif
+
+#define BUILD_TARGET_HAS_NOTIFICATION (MAC_OS_X_VERSION_MAX_ALLOWED >= 101000)
+#define BUILD_TARGET_HAS_UN_FRAMEWORK (MAC_OS_X_VERSION_MAX_ALLOWED >= 101400)
+#if MAC_OS_X_VERSION_MAX_ALLOWED > 101500
+#define ALERT_OPTION UNNotificationPresentationOptionList | \
+ UNNotificationPresentationOptionBanner
+#else
+#define ALERT_OPTION UNNotificationPresentationOptionAlert
+#endif
+
+#if BUILD_TARGET_HAS_UN_FRAMEWORK
+#import <UserNotifications/UserNotifications.h>
+static NSString *TkNotificationCategory;
+#endif
+
+#if BUILD_TARGET_HAS_NOTIFICATION
+
+/*
+ * Class declaration for TkStatusItem.
+ */
+
+@interface TkStatusItem: NSObject {
+ NSStatusItem * statusItem;
+ NSStatusBar * statusBar;
+ NSImage * icon;
+ NSString * tooltip;
+ Tcl_Interp * interp;
+ Tcl_Obj * b1_callback;
+ Tcl_Obj * b3_callback;
+}
+
+- (id) init : (Tcl_Interp *) interp;
+- (void) setImagewithImage : (NSImage *) image;
+- (void) setTextwithString : (NSString *) string;
+- (void) setB1Callback : (Tcl_Obj *) callback;
+- (void) setB3Callback : (Tcl_Obj *) callback;
+- (void) clickOnStatusItem;
+- (void) dealloc;
+
+@end
+
+/*
+ * Class declaration for TkNSNotifier. A TkNSNotifier object has no attributes
+ * but implements the NSUserNotificationCenterDelegate protocol. It also has
+ * one additional method which posts a user notification. There is one
+ * TkNSNotifier for the application, shared by all interpreters.
+ */
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+@interface TkNSNotifier: NSObject {
+}
+
+/*
+ * Post a notification.
+ */
+
+- (void) postNotificationWithTitle : (NSString *) title message: (NSString *) detail;
+
+/*
+ * The following methods comprise the NSUserNotificationCenterDelegate protocol.
+ */
+
+- (void) userNotificationCenter:(NSUserNotificationCenter *)center
+ didDeliverNotification:(NSUserNotification *)notification;
+
+- (void) userNotificationCenter:(NSUserNotificationCenter *)center
+ didActivateNotification:(NSUserNotification *)notification;
+
+- (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center
+ shouldPresentNotification:(NSUserNotification *)notification;
+
+@end
+#pragma clang diagnostic pop
+
+/*
+ * The singleton instance of TkNSNotifier shared by all interpreters in this
+ * application.
+ */
+
+static TkNSNotifier *NSnotifier = nil;
+
+#if BUILD_TARGET_HAS_UN_FRAMEWORK
+
+/*
+ * Class declaration for TkUNNotifier. A TkUNNotifier object has no attributes
+ * but implements the UNUserNotificationCenterDelegate protocol It also has two
+ * additional methods. One requests authorization to post notification via the
+ * UserNotification framework and the other posts a user notification. There is
+ * at most one TkUNNotifier for the application, shared by all interpreters.
+ */
+
+@interface TkUNNotifier: NSObject {
+}
+
+ /*
+ * Request authorization to post a notification.
+ */
+
+- (void) requestAuthorization;
+
+/*
+ * Post a notification.
+ */
+
+- (void) postNotificationWithTitle : (NSString *) title message: (NSString *) detail;
+
+/*
+ * The following methods comprise the UNNotificationCenterDelegate protocol:
+ */
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ didReceiveNotificationResponse:(UNNotificationResponse *)response
+ withCompletionHandler:(void (^)(void))completionHandler;
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ willPresentNotification:(UNNotification *)notification
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler;
+
+- (void)userNotificationCenter:(UNUserNotificationCenter *)center
+ openSettingsForNotification:(UNNotification *)notification;
+
+@end
+
+/*
+ * The singleton instance of TkUNNotifier shared by all interpeters is stored
+ * in this static variable.
+ */
+
+static TkUNNotifier *UNnotifier = nil;
+
+#endif
+
+/*
+ * Class declaration for TkStatusItem. A TkStatusItem represents an icon posted
+ * on the status bar located on the right side of the MenuBar. Each interpreter
+ * may have at most one TkStatusItem. A pointer to the TkStatusItem belonging
+ * to an interpreter is stored as the clientData of the MacSystrayObjCmd instance
+ * in that interpreter. It will be NULL until the tk systray command is executed
+ * by the interpreter.
+ */
+
+@implementation TkStatusItem : NSObject
+
+- (id) init : (Tcl_Interp *) interpreter {
+ [super init];
+ statusBar = [NSStatusBar systemStatusBar];
+ statusItem = [[statusBar statusItemWithLength:NSVariableStatusItemLength] retain];
+ statusItem.button.target = self;
+ statusItem.button.action = @selector(clickOnStatusItem);
+ [statusItem.button sendActionOn : NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp];
+ statusItem.visible = YES;
+ interp = interpreter;
+ b1_callback = NULL;
+ b3_callback = NULL;
+ return self;
+}
+
+- (void) setImagewithImage : (NSImage *) image
+{
+ icon = nil;
+ icon = image;
+ statusItem.button.image = icon;
+}
+
+- (void) setTextwithString : (NSString *) string
+{
+ tooltip = nil;
+ tooltip = string;
+ statusItem.button.toolTip = tooltip;
+}
+
+- (void) setB1Callback : (Tcl_Obj *) obj
+{
+ if (obj != NULL) {
+ Tcl_IncrRefCount(obj);
+ }
+ if (b1_callback != NULL) {
+ Tcl_DecrRefCount(b1_callback);
+ }
+ b1_callback = obj;
+}
+
+- (void) setB3Callback : (Tcl_Obj *) obj
+{
+ if (obj != NULL) {
+ Tcl_IncrRefCount(obj);
+ }
+ if (b3_callback != NULL) {
+ Tcl_DecrRefCount(b3_callback);
+ }
+ b3_callback = obj;
+}
+
+- (void) clickOnStatusItem
+{
+ NSEvent *event = [NSApp currentEvent];
+ if (([event type] == NSEventTypeLeftMouseUp) && (b1_callback != NULL)) {
+ int result = Tcl_EvalObjEx(interp, b1_callback, TCL_EVAL_GLOBAL);
+ if (result != TCL_OK) {
+ Tcl_BackgroundException(interp, result);
+ }
+ } else {
+ if (([event type] == NSEventTypeRightMouseUp) && (b3_callback != NULL)) {
+ int result = Tcl_EvalObjEx(interp, b3_callback, TCL_EVAL_GLOBAL);
+ if (result != TCL_OK) {
+ Tcl_BackgroundException(interp, result);
+ }
+ }
+ }
+}
+- (void) dealloc
+{
+ [statusBar removeStatusItem: statusItem];
+ if (b1_callback != NULL) {
+ Tcl_DecrRefCount(b1_callback);
+ }
+ if (b3_callback != NULL) {
+ Tcl_DecrRefCount(b3_callback);
+ }
+ [super dealloc];
+}
+
+@end
+
+/*
+ * Type used for the ClientData of a MacSystrayObjCmd instance.
+ */
+
+typedef TkStatusItem** StatusItemInfo;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+@implementation TkNSNotifier : NSObject
+
+- (void) postNotificationWithTitle : (NSString * ) title
+ message: (NSString * ) detail
+{
+ NSUserNotification *notification;
+ NSUserNotificationCenter *center;
+
+ center = [NSUserNotificationCenter defaultUserNotificationCenter];
+ notification = [[NSUserNotification alloc] init];
+ notification.title = title;
+ notification.informativeText = detail;
+ notification.soundName = NSUserNotificationDefaultSoundName;
+ DEBUG_LOG("Sending NSNotification.\n");
+ [center deliverNotification:notification];
+}
+
+/*
+ * Implementation of the NSUserNotificationDelegate protocol.
+ */
+
+- (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
+ shouldPresentNotification: (NSUserNotification *)notification
+{
+ (void) center;
+ (void) notification;
+
+ return YES;
+}
+
+- (void) userNotificationCenter:(NSUserNotificationCenter *)center
+ didDeliverNotification:(NSUserNotification *)notification
+{
+ (void) center;
+ (void) notification;
+}
+
+- (void) userNotificationCenter:(NSUserNotificationCenter *)center
+ didActivateNotification:(NSUserNotification *)notification
+{
+ (void) center;
+ (void) notification;
+}
+
+@end
+#pragma clang diagnostic pop
+
+/*
+ * Static variable which records whether the app is authorized to send
+ * notifications via the UNUserNotificationCenter.
+ */
+
+#if BUILD_TARGET_HAS_UN_FRAMEWORK
+
+@implementation TkUNNotifier : NSObject
+
+- (void) requestAuthorization
+{
+ UNUserNotificationCenter *center;
+ UNAuthorizationOptions options = UNAuthorizationOptionAlert |
+ UNAuthorizationOptionSound |
+ UNAuthorizationOptionBadge |
+ UNAuthorizationOptionProvidesAppNotificationSettings;
+ if (![NSApp isSigned]) {
+
+ /*
+ * No point in even asking.
+ */
+
+ DEBUG_LOG("Unsigned app: UNUserNotifications are not available.\n");
+ return;
+ }
+
+ center = [UNUserNotificationCenter currentNotificationCenter];
+ [center requestAuthorizationWithOptions: options
+ completionHandler: ^(BOOL granted, NSError* error)
+ {
+ if (error || granted == NO) {
+ DEBUG_LOG("Authorization for UNUserNotifications denied\n");
+ }
+ }];
+}
+
+- (void) postNotificationWithTitle: (NSString * ) title
+ message: (NSString * ) detail
+{
+ UNUserNotificationCenter *center;
+ UNMutableNotificationContent* content;
+ UNNotificationRequest *request;
+ center = [UNUserNotificationCenter currentNotificationCenter];
+ center.delegate = (id) self;
+ content = [[UNMutableNotificationContent alloc] init];
+ content.title = title;
+ content.body = detail;
+ content.sound = [UNNotificationSound defaultSound];
+ content.categoryIdentifier = TkNotificationCategory;
+ request = [UNNotificationRequest
+ requestWithIdentifier:[[NSUUID UUID] UUIDString]
+ content:content
+ trigger:nil
+ ];
+ [center addNotificationRequest: request
+ withCompletionHandler: ^(NSError* error) {
+ if (error) {
+ DEBUG_LOG("addNotificationRequest: error = %s\n", \
+ [NSString stringWithFormat:@"%@", \
+ error.userInfo].UTF8String);
+ }
+ }];
+}
+
+/*
+ * Implementation of the UNUserNotificationDelegate protocol.
+ */
+
+- (void) userNotificationCenter:(UNUserNotificationCenter *)center
+ didReceiveNotificationResponse:(UNNotificationResponse *)response
+ withCompletionHandler:(void (^)(void))completionHandler
+{
+ /*
+ * Called when the user dismisses a notification.
+ */
+
+ DEBUG_LOG("didReceiveNotification\n");
+ completionHandler();
+}
+
+- (void) userNotificationCenter:(UNUserNotificationCenter *)center
+ willPresentNotification:(UNNotification *)notification
+ withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
+{
+
+ /*
+ * This is called before presenting a notification, even when the user has
+ * turned off notifications.
+ */
+
+ DEBUG_LOG("willPresentNotification\n");
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
+ if (@available(macOS 11.0, *)) {
+ completionHandler(ALERT_OPTION);
+ }
+#endif
+}
+
+- (void) userNotificationCenter:(UNUserNotificationCenter *)center
+ openSettingsForNotification:(UNNotification *)notification
+{
+ DEBUG_LOG("openSettingsForNotification\n");
+ // Does something need to be done here?
+}
+
+@end
+
+#endif
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MacSystrayDestroy --
+ *
+ * Removes an intepreters icon from the status bar.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The icon is removed and memory is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+MacSystrayDestroy(
+ ClientData clientData,
+ TCL_UNUSED(Tcl_Interp *))
+{
+ StatusItemInfo info = (StatusItemInfo)clientData;
+ if (info) {
+ [*info release];
+ ckfree(info);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MacSystrayObjCmd --
+ *
+ * Main command for creating, displaying, and removing icons from the
+ * status bar.
+ *
+ * Results:
+ *
+ * A standard Tcl result.
+ *
+ * Side effects:
+ *
+ * Management of icon display in the status bar.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+MacSystrayObjCmd(
+ void *clientData,
+ Tcl_Interp * interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ Tk_Image tk_image;
+ int result, idx;
+ static const char *options[] =
+ {"create", "modify", "destroy", NULL};
+ typedef enum {TRAY_CREATE, TRAY_MODIFY, TRAY_DESTROY} optionsEnum;
+ static const char *modifyOptions[] =
+ {"image", "text", "b1_callback", "b3_callback", NULL};
+ typedef enum {TRAY_IMAGE, TRAY_TEXT, TRAY_B1_CALLBACK, TRAY_B3_CALLBACK
+ } modifyOptionsEnum;
+
+ if ([NSApp macOSVersion] < 101000) {
+ Tcl_AppendResult(interp,
+ "StatusItem icons not supported on macOS versions lower than 10.10",
+ NULL);
+ return TCL_OK;
+ }
+
+ StatusItemInfo info = (StatusItemInfo)clientData;
+ TkStatusItem *statusItem = *info;
+
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "create | modify | destroy");
+ return TCL_ERROR;
+ }
+
+ result = Tcl_GetIndexFromObjStruct(interp, objv[1], options,
+ sizeof(char *), "command", 0, &idx);
+
+ if (result != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch((optionsEnum)idx) {
+ case TRAY_CREATE: {
+
+ if (objc < 3 || objc > 6) {
+ Tcl_WrongNumArgs(interp, 1, objv, "create -image -text -button1 -button3");
+ return TCL_ERROR;
+ }
+
+ if (statusItem == NULL) {
+ statusItem = [[TkStatusItem alloc] init: interp];
+ *info = statusItem;
+ } else {
+ Tcl_AppendResult(interp, "Only one system tray icon supported per interpreter", NULL);
+ return TCL_ERROR;
+ }
+
+ /*
+ * Create the icon.
+ */
+
+ int width, height;
+ Tk_Window tkwin = Tk_MainWindow(interp);
+ TkWindow *winPtr = (TkWindow *)tkwin;
+ Display *d = winPtr->display;
+ NSImage *icon;
+
+ tk_image = Tk_GetImage(interp, tkwin, Tcl_GetString(objv[2]), NULL, NULL);
+ if (tk_image == NULL) {
+ return TCL_ERROR;
+ }
+
+ Tk_SizeOfImage(tk_image, &width, &height);
+ if (width != 0 && height != 0) {
+ icon = TkMacOSXGetNSImageFromTkImage(d, tk_image,
+ width, height);
+ [statusItem setImagewithImage: icon];
+ Tk_FreeImage(tk_image);
+ }
+
+ /*
+ * Set the text for the tooltip.
+ */
+
+ NSString *tooltip = [NSString stringWithUTF8String: Tcl_GetString(objv[3])];
+ if (tooltip == nil) {
+ Tcl_AppendResult(interp, " unable to set tooltip for systray icon", NULL);
+ return TCL_ERROR;
+ }
+
+ [statusItem setTextwithString: tooltip];
+
+ /*
+ * Set the proc for the callback.
+ */
+
+ [statusItem setB1Callback : (objc > 4) ? objv[4] : NULL];
+ [statusItem setB3Callback : (objc > 5) ? objv[5] : NULL];
+ break;
+
+ }
+ case TRAY_MODIFY: {
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 1, objv, "modify object item");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Modify the icon.
+ */
+
+ result = Tcl_GetIndexFromObjStruct(interp, objv[2], modifyOptions,
+ sizeof(char *), "option", 0, &idx);
+
+ if (result != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((modifyOptionsEnum)idx) {
+ case TRAY_IMAGE: {
+ Tk_Window tkwin = Tk_MainWindow(interp);
+ TkWindow *winPtr = (TkWindow *)tkwin;
+ Display *d = winPtr -> display;
+ NSImage *icon;
+ int width, height;
+
+ tk_image = Tk_GetImage(interp, tkwin, Tcl_GetString(objv[3]), NULL, NULL);
+ if (tk_image == NULL) {
+ Tcl_AppendResult(interp, " unable to obtain image for systray icon",
+ NULL);
+ return TCL_ERROR;
+ }
+
+ Tk_SizeOfImage(tk_image, &width, &height);
+ if (width != 0 && height != 0) {
+ icon = TkMacOSXGetNSImageFromTkImage(d, tk_image,
+ width, height);
+ [statusItem setImagewithImage: icon];
+ }
+ Tk_FreeImage(tk_image);
+ break;
+ }
+
+ /*
+ * Modify the text for the tooltip.
+ */
+
+ case TRAY_TEXT: {
+ NSString *tooltip = [NSString stringWithUTF8String:Tcl_GetString(objv[3])];
+ if (tooltip == nil) {
+ Tcl_AppendResult(interp, "unable to set tooltip for systray icon",
+ NULL);
+ return TCL_ERROR;
+ }
+
+ [statusItem setTextwithString: tooltip];
+ break;
+ }
+
+ /*
+ * Modify the proc for the callback.
+ */
+
+ case TRAY_B1_CALLBACK: {
+ [statusItem setB1Callback : objv[3]];
+ break;
+ }
+ case TRAY_B3_CALLBACK: {
+ [statusItem setB3Callback : objv[3]];
+ break;
+ }
+ }
+ break;
+ }
+
+ case TRAY_DESTROY: {
+ /*
+ * Set all properties to nil, and release statusItem.
+ */
+ [statusItem setImagewithImage: nil];
+ [statusItem setTextwithString: nil];
+ [statusItem setB1Callback : NULL];
+ [statusItem setB3Callback : NULL];
+ [statusItem release];
+ *info = NULL;
+ statusItem = NULL;
+ break;
+ }
+ }
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SysNotifyObjCmd --
+ *
+ * Create system notification.
+ *
+ * Results:
+ *
+ * A standard Tcl result.
+ *
+ * Side effects:
+ *
+ * System notifications are posted.
+ *
+ *-------------------------------z---------------------------------------
+ */
+
+static int SysNotifyObjCmd(
+ TCL_UNUSED(void *),
+ Tcl_Interp * interp,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 1, objv, "title message");
+ return TCL_ERROR;
+ }
+
+ if ([NSApp macOSVersion] < 101000) {
+ Tcl_AppendResult(interp,
+ "Notifications not supported on macOS versions lower than 10.10",
+ NULL);
+ return TCL_OK;
+ }
+
+ NSString *title = [NSString stringWithUTF8String: Tcl_GetString(objv[1])];
+ NSString *message = [NSString stringWithUTF8String: Tcl_GetString(objv[2])];
+
+ /*
+ * Update the authorization status in case the user enabled or disabled
+ * notifications after the app started up.
+ */
+
+#if BUILD_TARGET_HAS_UN_FRAMEWORK
+
+ if (UNnotifier && [NSApp isSigned]) {
+ UNUserNotificationCenter *center;
+
+ center = [UNUserNotificationCenter currentNotificationCenter];
+ [center getNotificationSettingsWithCompletionHandler:
+ ^(UNNotificationSettings *settings)
+ {
+#if !defined(DEBUG)
+ (void) settings;
+#endif
+ DEBUG_LOG("Reported authorization status is %ld\n",
+ settings.authorizationStatus);
+ }];
+ }
+
+#endif
+
+ if ([NSApp macOSVersion] < 101400 || ![NSApp isSigned]) {
+ DEBUG_LOG("Using the NSUserNotificationCenter\n");
+ [NSnotifier postNotificationWithTitle : title message: message];
+ } else {
+
+#if BUILD_TARGET_HAS_UN_FRAMEWORK
+
+ DEBUG_LOG("Using the UNUserNotificationCenter\n");
+ [UNnotifier postNotificationWithTitle : title message: message];
+#endif
+ }
+
+ return TCL_OK;
+}
+
+#endif // if BUILD_TARGET_HAS_NOTIFICATION
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MacSystrayInit --
+ *
+ * Initialize this package and create script-level commands.
+ * This is called from TkpInit for each interpreter.
+ *
+ * Results:
+ *
+ * A standard Tcl result.
+ *
+ * Side effects:
+ *
+ * The tk systray and tk sysnotify commands are installed in an
+ * interpreter
+ *
+ *----------------------------------------------------------------------
+ */
+
+#if BUILD_TARGET_HAS_NOTIFICATION
+
+int
+MacSystrayInit(Tcl_Interp *interp)
+{
+
+ /*
+ * Initialize the TkStatusItem for this interpreter and, if necessary,
+ * the shared TkNSNotifier and TkUNNotifier.
+ */
+
+ StatusItemInfo info = (StatusItemInfo) ckalloc(sizeof(StatusItemInfo));
+ *info = 0;
+
+ if (NSnotifier == nil) {
+ NSnotifier = [[TkNSNotifier alloc] init];
+ }
+
+#if BUILD_TARGET_HAS_UN_FRAMEWORK
+
+ if (@available(macOS 10.14, *)) {
+ UNUserNotificationCenter *center;
+ UNNotificationCategory *category;
+ NSSet *categories;
+
+ if (UNnotifier == nil) {
+ UNnotifier = [[TkUNNotifier alloc] init];
+
+ /*
+ * Request authorization to use the UserNotification framework. If
+ * the app code is signed and there are no notification preferences
+ * settings for this app, a dialog will be opened to prompt the
+ * user to choose settings. Note that the request is asynchronous,
+ * so even if the preferences setting exists the result is not
+ * available immediately.
+ */
+
+ [UNnotifier requestAuthorization];
+ }
+ TkNotificationCategory = @"Basic Tk Notification";
+ center = [UNUserNotificationCenter currentNotificationCenter];
+ center = [UNUserNotificationCenter currentNotificationCenter];
+ category = [UNNotificationCategory
+ categoryWithIdentifier:TkNotificationCategory
+ actions:@[]
+ intentIdentifiers:@[]
+ options: UNNotificationCategoryOptionNone];
+ categories = [NSSet setWithObjects:category, nil];
+ [center setNotificationCategories: categories];
+ }
+#endif
+
+ Tcl_CreateObjCommand(interp, "::tk::systray::_systray", MacSystrayObjCmd, info,
+ (Tcl_CmdDeleteProc *)MacSystrayDestroy);
+ Tcl_CreateObjCommand(interp, "::tk::sysnotify::_sysnotify", SysNotifyObjCmd, NULL, NULL);
+ return TCL_OK;
+}
+
+#else
+
+int
+MacSystrayInit(TCL_UNUSED(Tcl_Interp *))
+{
+ return TCL_OK;
+}
+
+#endif // BUILD_TARGET_HAS_NOTIFICATION
+
+/*
+ * Local Variables:
+ * mode: objc
+ * c-basic-offset: 4
+ * fill-column: 79
+ * coding: utf-8
+ * End:
+ */