summaryrefslogtreecommitdiffstats
path: root/macosx
diff options
context:
space:
mode:
Diffstat (limited to 'macosx')
-rw-r--r--macosx/tkMacOSXDialog.c60
-rw-r--r--macosx/tkMacOSXHLEvents.c26
-rw-r--r--macosx/tkMacOSXInit.c1
-rw-r--r--macosx/tkMacOSXPort.h13
-rw-r--r--macosx/tkMacOSXPrint.c353
-rw-r--r--macosx/tkMacOSXPrivate.h1
-rw-r--r--macosx/tkMacOSXSysTray.c424
-rw-r--r--macosx/tkMacOSXWm.c77
8 files changed, 521 insertions, 434 deletions
diff --git a/macosx/tkMacOSXDialog.c b/macosx/tkMacOSXDialog.c
index 1123346..9445b0c 100644
--- a/macosx/tkMacOSXDialog.c
+++ b/macosx/tkMacOSXDialog.c
@@ -350,49 +350,41 @@ static NSInteger showOpenSavePanel(
FilePanelCallbackInfo *callbackInfo)
{
NSInteger modalReturnCode;
+ int OSVersion = [NSApp macOSVersion];
- if (parent && ![parent attachedSheet]) {
- [panel beginSheetModalForWindow:parent
- completionHandler:^(NSModalResponse returnCode) {
- [NSApp tkFilePanelDidEnd:panel
- returnCode:returnCode
- contextInfo:callbackInfo ];
- }];
-
- /*
- * The sheet has been prepared, so now we have to run it as a modal
- * window. Using [NSApp runModalForWindow:] on macOS 10.15 or later
- * generates warnings on stderr. But using [NSOpenPanel runModal] or
- * [NSSavePanel runModal] on 10.14 or earler does not cause the
- * completion handler to run when the panel is closed.
- */
+ /*
+ * Use a sheet if -parent is specified (unless there is already a sheet).
+ */
- if ([NSApp macOSVersion] > 101400) {
- modalReturnCode = [panel runModal];
- } else {
+ if (parent && ![parent attachedSheet]) {
+ if (OSVersion < 101500) {
+ [panel beginSheetModalForWindow:parent
+ completionHandler:^(NSModalResponse returnCode) {
+ [NSApp tkFilePanelDidEnd:panel
+ returnCode:returnCode
+ contextInfo:callbackInfo ];
+ }];
modalReturnCode = [NSApp runModalForWindow:panel];
- }
- } else {
-
- /*
- * For the standalone file dialog, completion handlers do not work
- * at all on macOS 10.14 and earlier.
- */
-
- if ([NSApp macOSVersion] > 101400) {
- [panel beginWithCompletionHandler:^(NSModalResponse returnCode) {
+ } else if (OSVersion < 110000) {
+ [panel beginSheetModalForWindow:parent
+ completionHandler:^(NSModalResponse returnCode) {
[NSApp tkFilePanelDidEnd:panel
- returnCode:returnCode
- contextInfo:callbackInfo ];
- }];
+ returnCode:returnCode
+ contextInfo:callbackInfo ];
+ }];
modalReturnCode = [panel runModal];
} else {
+ [parent beginSheet: panel completionHandler:nil];
modalReturnCode = [panel runModal];
[NSApp tkFilePanelDidEnd:panel
- returnCode:modalReturnCode
- contextInfo:callbackInfo ];
- [panel close];
+ returnCode:modalReturnCode
+ contextInfo:callbackInfo ];
}
+ } else {
+ modalReturnCode = [panel runModal];
+ [NSApp tkFilePanelDidEnd:panel
+ returnCode:modalReturnCode
+ contextInfo:callbackInfo ];
}
return callbackInfo->cmdObj ? modalOther : modalReturnCode;
}
diff --git a/macosx/tkMacOSXHLEvents.c b/macosx/tkMacOSXHLEvents.c
index 7cd8344..aefc63c 100644
--- a/macosx/tkMacOSXHLEvents.c
+++ b/macosx/tkMacOSXHLEvents.c
@@ -59,6 +59,7 @@ static const char launchURLProc[] = "::tk::mac::LaunchURL";
static const char printDocProc[] = "::tk::mac::PrintDocument";
static const char scriptFileProc[] = "::tk::mac::DoScriptFile";
static const char scriptTextProc[] = "::tk::mac::DoScriptText";
+static const char getSdefProc[] = "::tk::mac::GetDynamicSdef";
#pragma mark TKApplication(TKHLEvents)
@@ -386,6 +387,22 @@ static const char scriptTextProc[] = "::tk::mac::DoScriptText";
ProcessAppleEvent((ClientData)AEInfo);
}
+- (void)handleGetSDEFEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
+ AppleEventInfo *AEInfo = (AppleEventInfo *)ckalloc(sizeof(AppleEventInfo));
+ Tcl_DString *sdefCommand = &AEInfo->command;
+ (void)replyEvent;
+
+ Tcl_DStringInit(sdefCommand);
+ Tcl_DStringAppend(sdefCommand, getSdefProc, -1);
+ AEInfo->interp = _eventInterp;
+ AEInfo->procedure = getSdefProc;
+ AEInfo->replyEvent = nil;
+ Tcl_DoWhenIdle(ProcessAppleEvent, (ClientData)AEInfo);
+ AEInfo->retryCount = 0;
+ ProcessAppleEvent((ClientData)AEInfo);
+
+}
+
@end
#pragma mark -
@@ -523,6 +540,15 @@ TkMacOSXInitAppleEvents(
andSelector:@selector(handleURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass andEventID:kAEGetURL];
+ /*
+ * We do not load our sdef dynamically but this event handler
+ * is required to silence error messages from inline execution
+ * of AppleScript at the Objective-C level.
+ */
+ [aeManager setEventHandler:NSApp
+ andSelector:@selector(handleGetSDEFEvent:withReplyEvent:)
+ forEventClass:'ascr' andEventID:'gsdf'];
+
}
}
diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c
index 05be18c..a1de785 100644
--- a/macosx/tkMacOSXInit.c
+++ b/macosx/tkMacOSXInit.c
@@ -642,6 +642,7 @@ TkMacOSXGetAppPathObjCmd(
CFRelease(mainBundleURL);
CFRelease(appPath);
MacSystrayInit(interp);
+ MacPrint_Init(interp);
return TCL_OK;
}
diff --git a/macosx/tkMacOSXPort.h b/macosx/tkMacOSXPort.h
index cf8f615..3f4ac44 100644
--- a/macosx/tkMacOSXPort.h
+++ b/macosx/tkMacOSXPort.h
@@ -72,6 +72,19 @@
#endif
/*
+ * Used to tag functions that are only to be visible within the module being
+ * built and not outside it (where this is supported by the linker).
+ */
+
+#ifndef MODULE_SCOPE
+# ifdef __cplusplus
+# define MODULE_SCOPE extern "C"
+# else
+# define MODULE_SCOPE extern
+# endif
+#endif
+
+/*
* The following macro defines the number of fd_masks in an fd_set:
*/
diff --git a/macosx/tkMacOSXPrint.c b/macosx/tkMacOSXPrint.c
new file mode 100644
index 0000000..38cd1ca
--- /dev/null
+++ b/macosx/tkMacOSXPrint.c
@@ -0,0 +1,353 @@
+/*
+ * tkMacOSXPrint.c --
+ *
+ * This module implements native printing dialogs for macOS.
+ *
+ * Copyright © 2006 Apple Inc.
+ * Copyright © 2011-2021 Kevin Walzer/WordTech Communications LLC.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include <tcl.h>
+#include <tk.h>
+#include <tkInt.h>
+#include <Cocoa/Cocoa.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <tkMacOSXInt.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+/* Forward declarations of functions and variables. */
+NSString * fileName = nil;
+CFStringRef urlFile = NULL;
+int StartPrint(ClientData clientData, Tcl_Interp * interp,
+ int objc, Tcl_Obj * const objv[]);
+OSStatus FinishPrint(NSString *file, int buttonValue);
+int MacPrint_Init(Tcl_Interp * interp);
+
+/* Delegate class for print dialogs. */
+@interface PrintDelegate: NSObject
+ - (id) init;
+ - (void) printPanelDidEnd: (NSPrintPanel *) printPanel
+ returnCode: (int) returnCode
+ contextInfo: (void *) contextInfo;
+@end
+
+@implementation PrintDelegate
+- (id) init {
+ self = [super init];
+ return self;
+}
+
+- (void) printPanelDidEnd: (NSPrintPanel *) printPanel
+ returnCode: (int) returnCode
+ contextInfo: (void *) contextInfo {
+ /*
+ * Pass returnCode to FinishPrint function to determine how to
+ * handle.
+ */
+ FinishPrint(fileName, returnCode);
+}
+@end
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * StartPrint --
+ *
+ * Launch native print dialog.
+ *
+ * Results:
+ * Configures values and starts print process.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+StartPrint(
+ ClientData clientData,
+ Tcl_Interp * interp,
+ int objc,
+ Tcl_Obj *const objv[])
+{
+ (void) clientData;
+ NSPrintInfo * printInfo = [NSPrintInfo sharedPrintInfo];
+ NSPrintPanel * printPanel = [NSPrintPanel printPanel];
+ int accepted;
+ PMPrintSession printSession;
+ PMPageFormat pageFormat;
+ PMPrintSettings printSettings;
+ OSStatus status = noErr;
+
+ /* Check for proper number of arguments. */
+ if (objc < 2) {
+ Tcl_WrongNumArgs(interp, 1, objv, "file");
+ return TCL_ERROR;
+ }
+
+ fileName = [NSString stringWithUTF8String: Tcl_GetString(objv[1])];
+ urlFile = (CFStringRef) fileName;
+ CFRetain(urlFile);
+
+ /* Initialize the delegate for the callback from the page panel. */
+ PrintDelegate * printDelegate = [[PrintDelegate alloc] init];
+
+ status = PMCreateSession( & printSession);
+ if (status != noErr) {
+ NSLog(@ "Error creating print session.");
+ return TCL_ERROR;
+ }
+
+ status = PMCreatePrintSettings( & printSettings);
+ if (status != noErr) {
+ NSLog(@ "Error creating print settings.");
+ return TCL_ERROR;
+ }
+
+ status = PMSessionDefaultPrintSettings(printSession, printSettings);
+ if (status != noErr) {
+ NSLog(@ "Error creating default print settings.");
+ return TCL_ERROR;
+ }
+
+ printSession = (PMPrintSession)[printInfo PMPrintSession];
+ pageFormat = (PMPageFormat)[printInfo PMPageFormat];
+ printSettings = (PMPrintSettings)[printInfo PMPrintSettings];
+
+ accepted = [printPanel runModalWithPrintInfo: printInfo];
+ [printDelegate printPanelDidEnd: printPanel
+ returnCode: accepted
+ contextInfo: printInfo];
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FinishPrint --
+ *
+ * Handles print process based on input from dialog.
+ *
+ * Results:
+ * Completes print process.
+ *
+ *----------------------------------------------------------------------
+ */
+
+OSStatus
+FinishPrint(
+ NSString *file,
+ int buttonValue)
+{
+ NSPrintInfo * printInfo = [NSPrintInfo sharedPrintInfo];
+ PMPrintSession printSession;
+ PMPageFormat pageFormat;
+ PMPrintSettings printSettings;
+ OSStatus status = noErr;
+ CFStringRef mimeType = NULL;
+
+ /*
+ * If value passed here is NSCancelButton, return noErr;
+ * otherwise printing will occur regardless of value.
+ */
+ if (buttonValue == NSModalResponseCancel) {
+ return noErr;
+ }
+
+ status = PMCreateSession( & printSession);
+ if (status != noErr) {
+ NSLog(@ "Error creating print session.");
+ return status;
+ }
+
+ status = PMCreatePrintSettings( & printSettings);
+ if (status != noErr) {
+ NSLog(@ "Error creating print settings.");
+ return status;
+ }
+
+ status = PMSessionDefaultPrintSettings(printSession, printSettings);
+ if (status != noErr) {
+ NSLog(@ "Error creating default print settings.");
+ return status;
+ }
+
+ printSession = (PMPrintSession)[printInfo PMPrintSession];
+ pageFormat = (PMPageFormat)[printInfo PMPageFormat];
+ printSettings = (PMPrintSettings)[printInfo PMPrintSettings];
+
+ /*Handle print operation.*/
+ if (buttonValue == NSModalResponseOK) {
+
+ if (urlFile == NULL) {
+ NSLog(@ "Could not get file to print.");
+ return noErr;
+ }
+
+ fileName = file;
+
+ CFURLRef printURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, urlFile, kCFURLPOSIXPathStyle, false);
+
+ PMPrinter currentPrinter;
+ PMDestinationType printDestination;
+
+ /*Get the intended destination.*/
+ status = PMSessionGetDestinationType(printSession, printSettings, & printDestination);
+
+ /*Destination is printer. Send file to printer.*/
+ if (status == noErr && printDestination == kPMDestinationPrinter) {
+
+ status = PMSessionGetCurrentPrinter(printSession, & currentPrinter);
+ if (status == noErr) {
+ CFArrayRef mimeTypes;
+ status = PMPrinterGetMimeTypes(currentPrinter, printSettings, & mimeTypes);
+ if (status == noErr && mimeTypes != NULL) {
+ mimeType = CFSTR("application/pdf");
+ if (CFArrayContainsValue(mimeTypes, CFRangeMake(0, CFArrayGetCount(mimeTypes)), mimeType)) {
+ status = PMPrinterPrintWithFile(currentPrinter, printSettings, pageFormat, mimeType, printURL);
+ CFRelease(urlFile);
+ return status;
+ }
+ }
+ }
+ }
+
+ /* Destination is file. Determine how to handle. */
+ if (status == noErr && printDestination == kPMDestinationFile) {
+ CFURLRef outputLocation = NULL;
+
+ status = PMSessionCopyDestinationLocation(printSession, printSettings, & outputLocation);
+ if (status == noErr) {
+ /*Get the source file and target destination, convert to strings.*/
+ CFStringRef sourceFile = CFURLCopyFileSystemPath(printURL, kCFURLPOSIXPathStyle);
+ CFStringRef savePath = CFURLCopyFileSystemPath(outputLocation, kCFURLPOSIXPathStyle);
+ NSString * sourcePath = (NSString * ) sourceFile;
+ NSString * finalPath = (NSString * ) savePath;
+ NSString * pathExtension = [finalPath pathExtension];
+ NSFileManager * fileManager = [NSFileManager defaultManager];
+ NSError * error = nil;
+
+ /*
+ * Is the target file a PDF? If so, copy print file
+ * to output location.
+ */
+ if ([pathExtension isEqualToString: @ "pdf"]) {
+
+ /*Make sure no file conflict exists.*/
+ if ([fileManager fileExistsAtPath: finalPath]) {
+ [fileManager removeItemAtPath: finalPath error: &error];
+ }
+ if ([fileManager fileExistsAtPath: sourcePath]) {
+ error = nil;
+ [fileManager copyItemAtPath: sourcePath toPath: finalPath error: & error];
+ }
+ return status;
+ }
+
+ /*
+ * Is the target file PostScript? If so, run print file
+ * through CUPS filter to convert back to PostScript.
+ */
+
+ if ([pathExtension isEqualToString: @ "ps"]) {
+ char source[5012];
+ char target[5012];
+ [sourcePath getCString: source maxLength: (sizeof source) encoding: NSUTF8StringEncoding];
+ [finalPath getCString: target maxLength: (sizeof target) encoding: NSUTF8StringEncoding];
+ /*Make sure no file conflict exists.*/
+ if ([fileManager fileExistsAtPath: finalPath]) {
+ [fileManager removeItemAtPath: finalPath error: &error];
+ }
+
+ /*
+ * Fork and start new process with command string. Thanks to Peter da Silva
+ * for assistance.
+ */
+ pid_t pid;
+ if ((pid = fork()) == -1) {
+ return -1;
+ } else if (pid == 0) {
+ /* Redirect output to file and silence debugging output.*/
+ dup2(open(target, O_RDWR | O_CREAT, 0777), 1);
+ dup2(open("/dev/null", O_WRONLY), 2);
+ execl("/usr/sbin/cupsfilter", "/usr/sbin/cupsfilter", "-m", "application/postscript", source, NULL);
+ exit(0);
+ }
+ return status;
+ }
+ }
+ }
+
+ /* Destination is preview. Open file in default application for PDF. */
+ if ((status == noErr) && (printDestination == kPMDestinationPreview)) {
+ CFStringRef urlpath = CFURLCopyFileSystemPath(printURL, kCFURLPOSIXPathStyle);
+ NSString * path = (NSString * ) urlpath;
+ NSURL * url = [NSURL fileURLWithPath: path];
+ NSWorkspace * ws = [NSWorkspace sharedWorkspace];
+ [ws openURL: url];
+ status = noErr;
+ return status;
+ }
+
+ /*
+ * If destination is not printer, file or preview,
+ * we do not support it. Display alert.
+ */
+
+ if (((status == noErr) && (printDestination != kPMDestinationPreview)) || ((status == noErr) && (printDestination != kPMDestinationFile)) || ((status == noErr) && (printDestination != kPMDestinationPrinter))) {
+
+ NSAlert * alert = [[[NSAlert alloc] init] autorelease];
+ [alert addButtonWithTitle: @ "OK"];
+
+ [alert setMessageText: @ "Unsupported Printing Operation"];
+ [alert setInformativeText: @ "This printing operation is not supported."];
+ [alert setAlertStyle: NSAlertStyleInformational];
+ [alert runModal];
+ return status;
+ }
+ }
+
+ /* Return because cancel button was clicked. */
+ if (buttonValue == NSModalResponseCancel) {
+ PMRelease(printSession);
+ return status;
+ }
+
+ return status;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MacPrint_Init--
+ *
+ * Initializes the printing module.
+ *
+ * Results:
+ * Printing module initialized.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int MacPrint_Init(Tcl_Interp * interp) {
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+ Tcl_CreateObjCommand(interp, "::tk::print::_print", StartPrint, (ClientData) NULL, (Tcl_CmdDeleteProc * ) NULL);
+ [pool release];
+ return TCL_OK;
+}
+
+/*
+ * Local Variables:
+ * mode: objc
+ * c-basic-offset: 4
+ * fill-column: 79
+ * coding: utf-8
+ * End:
+ */
diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h
index 6d2119c..bab722f 100644
--- a/macosx/tkMacOSXPrivate.h
+++ b/macosx/tkMacOSXPrivate.h
@@ -300,6 +300,7 @@ MODULE_SCOPE void TkMacOSXDrawAllViews(ClientData clientData);
MODULE_SCOPE void Ttk_MacOSXInit(void);
MODULE_SCOPE unsigned long TkMacOSXClearPixel(void);
MODULE_SCOPE int MacSystrayInit(Tcl_Interp *);
+MODULE_SCOPE int MacPrint_Init(Tcl_Interp *);
#pragma mark Private Objective-C Classes
diff --git a/macosx/tkMacOSXSysTray.c b/macosx/tkMacOSXSysTray.c
index a0f0829..c2e347f 100644
--- a/macosx/tkMacOSXSysTray.c
+++ b/macosx/tkMacOSXSysTray.c
@@ -19,71 +19,6 @@
#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.
*/
@@ -107,97 +42,7 @@ static NSString *TkNotificationCategory;
@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
@@ -297,163 +142,7 @@ static TkUNNotifier *UNnotifier = nil;
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
/*
*----------------------------------------------------------------------
@@ -727,49 +416,38 @@ static int SysNotifyObjCmd(
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.
+ * Using NSAppleScript API here allows us to use a single API rather
+ * than multiple, some deprecated, API's, and also allows notifications
+ * to work correctly without requiring Wish to be code-signed - an
+ * undocumented but apparently consistent requirement. And by calling
+ * NSAppleScript inline rather than shelling to out osascript,
+ * Wish shows correctly as the calling app rather than Script Editor.
*/
-#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
- }
+ NSString *title = [NSString stringWithUTF8String: Tcl_GetString(objv[1])];
+ NSString *message = [NSString stringWithUTF8String: Tcl_GetString(objv[2])];
+ NSMutableString *notify = [NSMutableString new];
+ [notify appendString: @"display notification "];
+ [notify appendString:@"\""];
+ [notify appendString:message];
+ [notify appendString:@"\""];
+ [notify appendString:@" with title \""];
+ [notify appendString:title];
+ [notify appendString:@"\""];
+ NSAppleScript *scpt = [[[NSAppleScript alloc] initWithSource:notify] autorelease];
+ NSDictionary *errorInfo;
+ NSAppleEventDescriptor *result = [scpt executeAndReturnError:&errorInfo];
+ NSString *info = [result stringValue];
+ const char* output = [info UTF8String];
+
+ Tcl_AppendResult(interp,
+ output,
+ NULL);
return TCL_OK;
}
-#endif // if BUILD_TARGET_HAS_NOTIFICATION
/*
*----------------------------------------------------------------------
@@ -791,73 +469,23 @@ static int SysNotifyObjCmd(
*----------------------------------------------------------------------
*/
-#if BUILD_TARGET_HAS_NOTIFICATION
-
int
MacSystrayInit(Tcl_Interp *interp)
{
/*
- * Initialize the TkStatusItem for this interpreter and, if necessary,
- * the shared TkNSNotifier and TkUNNotifier.
+ * Initialize the TkStatusItem for this interpreter.
*/
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:
diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c
index c59950b..69d9e70 100644
--- a/macosx/tkMacOSXWm.c
+++ b/macosx/tkMacOSXWm.c
@@ -243,6 +243,9 @@ static int WmGridCmd(Tk_Window tkwin, TkWindow *winPtr,
static int WmGroupCmd(Tk_Window tkwin, TkWindow *winPtr,
Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[]);
+static int WmIconbadgeCmd(Tk_Window tkwin, TkWindow *winPtr,
+ Tcl_Interp *interp, int objc,
+ Tcl_Obj *const objv[]);
static int WmIconbitmapCmd(Tk_Window tkwin, TkWindow *winPtr,
Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[]);
@@ -1191,7 +1194,7 @@ Tk_WmObjCmd(
static const char *const optionStrings[] = {
"aspect", "attributes", "client", "colormapwindows",
"command", "deiconify", "focusmodel", "forget",
- "frame", "geometry", "grid", "group",
+ "frame", "geometry", "grid", "group", "iconbadge",
"iconbitmap", "iconify", "iconmask", "iconname",
"iconphoto", "iconposition", "iconwindow",
"manage", "maxsize", "minsize", "overrideredirect",
@@ -1201,7 +1204,7 @@ Tk_WmObjCmd(
enum options {
WMOPT_ASPECT, WMOPT_ATTRIBUTES, WMOPT_CLIENT, WMOPT_COLORMAPWINDOWS,
WMOPT_COMMAND, WMOPT_DEICONIFY, WMOPT_FOCUSMODEL, WMOPT_FORGET,
- WMOPT_FRAME, WMOPT_GEOMETRY, WMOPT_GRID, WMOPT_GROUP,
+ WMOPT_FRAME, WMOPT_GEOMETRY, WMOPT_GRID, WMOPT_GROUP, WMOPT_ICONBADGE,
WMOPT_ICONBITMAP, WMOPT_ICONIFY, WMOPT_ICONMASK, WMOPT_ICONNAME,
WMOPT_ICONPHOTO, WMOPT_ICONPOSITION, WMOPT_ICONWINDOW,
WMOPT_MANAGE, WMOPT_MAXSIZE, WMOPT_MINSIZE, WMOPT_OVERRIDEREDIRECT,
@@ -1279,6 +1282,8 @@ Tk_WmObjCmd(
return WmGridCmd(tkwin, winPtr, interp, objc, objv);
case WMOPT_GROUP:
return WmGroupCmd(tkwin, winPtr, interp, objc, objv);
+ case WMOPT_ICONBADGE:
+ return WmIconbadgeCmd(tkwin, winPtr, interp, objc, objv);
case WMOPT_ICONBITMAP:
return WmIconbitmapCmd(tkwin, winPtr, interp, objc, objv);
case WMOPT_ICONIFY:
@@ -2338,6 +2343,74 @@ WmGroupCmd(
return TCL_OK;
}
+ /*----------------------------------------------------------------------
+ *
+ * WmIconbadgeCmd --
+ *
+ * This procedure is invoked to process the "wm iconbadge" Tcl command.
+ * See the user documentation for details on what it does.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+WmIconbadgeCmd(
+ TCL_UNUSED(Tk_Window), /* Main window of the application. */
+ TkWindow *winPtr, /* Toplevel to work with */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. */
+{
+ (void) winPtr;
+ NSString *label;
+
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 2, objv,"window badge");
+ return TCL_ERROR;
+ }
+
+ label = [NSString stringWithUTF8String:Tcl_GetString(objv[3])];
+
+ int number = [label intValue];
+ NSDockTile *dockicon = [NSApp dockTile];
+
+ /*
+ * First, check that the label is not a decimal. If it is,
+ * return an error.
+ */
+
+ if ([label containsString:@"."]) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "can't use \"%s\" as icon badge", Tcl_GetString(objv[3])));
+ return TCL_ERROR;
+ }
+
+ /*
+ * Next, check that label is an int, empty string, or exclamation
+ * point. If so, set the icon badge on the Dock icon. Otherwise,
+ * return an error.
+ */
+
+ NSArray *array = @[@"", @"!"];
+ if ([array containsObject: label]) {
+ [dockicon setBadgeLabel:label];
+ } else if (number > 0) {
+ NSString *str = [@(number) stringValue];
+ [dockicon setBadgeLabel:str];
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "can't use \"%s\" as icon badge", Tcl_GetString(objv[3])));
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+
/*
*----------------------------------------------------------------------
*