diff options
author | jan.nijtmans <nijtmans@users.sourceforge.net> | 2021-10-28 21:11:26 (GMT) |
---|---|---|
committer | jan.nijtmans <nijtmans@users.sourceforge.net> | 2021-10-28 21:11:26 (GMT) |
commit | 3f351dd65a460eed8c85334493d901b90275ffc5 (patch) | |
tree | 366c54b6c199e08238388ad793a3c22878d721a3 /macosx | |
parent | 8f7a2722bdec65b0ad86aeef5004187c8a91fc42 (diff) | |
parent | 0772b810e31ff67395a23cf07f9cb170125b0216 (diff) | |
download | tk-3f351dd65a460eed8c85334493d901b90275ffc5.zip tk-3f351dd65a460eed8c85334493d901b90275ffc5.tar.gz tk-3f351dd65a460eed8c85334493d901b90275ffc5.tar.bz2 |
Merge 8.7
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/tkMacOSXDialog.c | 60 | ||||
-rw-r--r-- | macosx/tkMacOSXHLEvents.c | 26 | ||||
-rw-r--r-- | macosx/tkMacOSXInit.c | 1 | ||||
-rw-r--r-- | macosx/tkMacOSXPort.h | 13 | ||||
-rw-r--r-- | macosx/tkMacOSXPrint.c | 353 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 1 | ||||
-rw-r--r-- | macosx/tkMacOSXSysTray.c | 424 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 77 |
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; +} + /* *---------------------------------------------------------------------- * |