diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2017-09-22 18:57:36 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2017-09-22 18:57:36 (GMT) |
commit | 04d4f0f83ae01daebf6001f40d1261464d185809 (patch) | |
tree | d1e4a8b1c22c47d70a369143d3406c524b5c1834 /tk8.6/macosx/tkMacOSXHLEvents.c | |
parent | 2aff4a96fa0286d875bddec0019648e2c6431cbc (diff) | |
parent | 1005c7e630baa54e58ac331f48bf876011deb6b3 (diff) | |
download | blt-04d4f0f83ae01daebf6001f40d1261464d185809.zip blt-04d4f0f83ae01daebf6001f40d1261464d185809.tar.gz blt-04d4f0f83ae01daebf6001f40d1261464d185809.tar.bz2 |
Merge commit '1005c7e630baa54e58ac331f48bf876011deb6b3' as 'tk8.6'
Diffstat (limited to 'tk8.6/macosx/tkMacOSXHLEvents.c')
-rw-r--r-- | tk8.6/macosx/tkMacOSXHLEvents.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/tk8.6/macosx/tkMacOSXHLEvents.c b/tk8.6/macosx/tkMacOSXHLEvents.c new file mode 100644 index 0000000..f5aeff0 --- /dev/null +++ b/tk8.6/macosx/tkMacOSXHLEvents.c @@ -0,0 +1,545 @@ +/* + * tkMacOSXHLEvents.c -- + * + * Implements high level event support for the Macintosh. Currently, the + * only event that really does anything is the Quit event. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright 2001-2009, Apple Inc. + * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> + * Copyright (c) 2015 Marc Culler + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tkMacOSXPrivate.h" +#include <sys/param.h> +#define URL_MAX_LENGTH (17 + MAXPATHLEN) + +/* + * This is a Tcl_Event structure that the Quit AppleEvent handler uses to + * schedule the ReallyKillMe function. + */ + +typedef struct KillEvent { + Tcl_Event header; /* Information that is standard for all + * events. */ + Tcl_Interp *interp; /* Interp that was passed to the Quit + * AppleEvent */ +} KillEvent; + +/* + * Static functions used only in this file. + */ + +static void tkMacOSXProcessFiles(NSAppleEventDescriptor* event, + NSAppleEventDescriptor* replyEvent, + Tcl_Interp *interp, + char* procedure); +static int MissedAnyParameters(const AppleEvent *theEvent); +static int ReallyKillMe(Tcl_Event *eventPtr, int flags); + +#pragma mark TKApplication(TKHLEvents) + +@implementation TKApplication(TKHLEvents) +- (void) terminate: (id) sender +{ + [self handleQuitApplicationEvent:Nil withReplyEvent:Nil]; +} + +- (void) preferences: (id) sender +{ + [self handleShowPreferencesEvent:Nil withReplyEvent:Nil]; +} + +- (void) handleQuitApplicationEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + KillEvent *eventPtr; + + if (_eventInterp) { + /* + * Call the exit command from the event loop, since you are not + * supposed to call ExitToShell in an Apple Event Handler. We put this + * at the head of Tcl's event queue because this message usually comes + * when the Mac is shutting down, and we want to kill the shell as + * quickly as possible. + */ + + eventPtr = ckalloc(sizeof(KillEvent)); + eventPtr->header.proc = ReallyKillMe; + eventPtr->interp = _eventInterp; + + Tcl_QueueEvent((Tcl_Event *) eventPtr, TCL_QUEUE_HEAD); + } +} + +- (void) handleOpenApplicationEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + Tcl_Interp *interp = _eventInterp; + + if (interp && + Tcl_FindCommand(_eventInterp, "::tk::mac::OpenApplication", NULL, 0)){ + int code = Tcl_EvalEx(_eventInterp, "::tk::mac::OpenApplication", + -1, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_BackgroundException(_eventInterp, code); + } + } +} + +- (void) handleReopenApplicationEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 + ProcessSerialNumber thePSN = {0, kCurrentProcess}; + SetFrontProcess(&thePSN); +#else + [[NSApplication sharedApplication] activateIgnoringOtherApps: YES]; +#endif + if (_eventInterp && Tcl_FindCommand(_eventInterp, + "::tk::mac::ReopenApplication", NULL, 0)) { + int code = Tcl_EvalEx(_eventInterp, "::tk::mac::ReopenApplication", + -1, TCL_EVAL_GLOBAL); + if (code != TCL_OK){ + Tcl_BackgroundException(_eventInterp, code); + } + } +} + +- (void) handleShowPreferencesEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + if (_eventInterp && + Tcl_FindCommand(_eventInterp, "::tk::mac::ShowPreferences", NULL, 0)){ + int code = Tcl_EvalEx(_eventInterp, "::tk::mac::ShowPreferences", + -1, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_BackgroundException(_eventInterp, code); + } + } +} + +- (void) handleOpenDocumentsEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + tkMacOSXProcessFiles(event, replyEvent, _eventInterp, "::tk::mac::OpenDocument"); +} + +- (void) handlePrintDocumentsEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + tkMacOSXProcessFiles(event, replyEvent, _eventInterp, "::tk::mac::PrintDocument"); +} + +- (void) handleDoScriptEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + OSStatus err; + const AEDesc *theDesc = nil; + DescType type = 0, initialType = 0; + Size actual; + int tclErr = -1; + char URLBuffer[1 + URL_MAX_LENGTH]; + char errString[128]; + char typeString[5]; + + /* + * The DoScript event receives one parameter that should be text data or a + * fileURL. + */ + + theDesc = [event aeDesc]; + if (theDesc == nil) { + return; + } + + err = AEGetParamPtr(theDesc, keyDirectObject, typeWildCard, &initialType, + NULL, 0, NULL); + if (err != noErr) { + sprintf(errString, "AEDoScriptHandler: GetParamDesc error %d", (int)err); + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, + errString, strlen(errString)); + return; + } + + if (MissedAnyParameters((AppleEvent*)theDesc)) { + sprintf(errString, "AEDoScriptHandler: extra parameters"); + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, + errString, strlen(errString)); + return; + } + + if (initialType == typeFileURL || initialType == typeAlias) { + /* + * The descriptor can be coerced to a file url. Source the file, or + * pass the path as a string argument to ::tk::mac::DoScriptFile if + * that procedure exists. + */ + err = AEGetParamPtr(theDesc, keyDirectObject, typeFileURL, &type, + (Ptr) URLBuffer, URL_MAX_LENGTH, &actual); + if (err == noErr && actual > 0){ + URLBuffer[actual] = '\0'; + NSString *urlString = [NSString stringWithUTF8String:(char*)URLBuffer]; + NSURL *fileURL = [NSURL URLWithString:urlString]; + Tcl_DString command; + Tcl_DStringInit(&command); + if (Tcl_FindCommand(_eventInterp, "::tk::mac::DoScriptFile", NULL, 0)){ + Tcl_DStringAppend(&command, "::tk::mac::DoScriptFile", -1); + } else { + Tcl_DStringAppend(&command, "source", -1); + } + Tcl_DStringAppendElement(&command, [[fileURL path] UTF8String]); + tclErr = Tcl_EvalEx(_eventInterp, Tcl_DStringValue(&command), + Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); + } + } else if (noErr == AEGetParamPtr(theDesc, keyDirectObject, typeUTF8Text, &type, + NULL, 0, &actual)) { + if (actual > 0) { + /* + * The descriptor can be coerced to UTF8 text. Evaluate as Tcl, or + * or pass the text as a string argument to ::tk::mac::DoScriptText + * if that procedure exists. + */ + char *data = ckalloc(actual + 1); + if (noErr == AEGetParamPtr(theDesc, keyDirectObject, typeUTF8Text, &type, + data, actual, NULL)) { + if (Tcl_FindCommand(_eventInterp, "::tk::mac::DoScriptText", NULL, 0)){ + Tcl_DString command; + Tcl_DStringInit(&command); + Tcl_DStringAppend(&command, "::tk::mac::DoScriptText", -1); + Tcl_DStringAppendElement(&command, data); + tclErr = Tcl_EvalEx(_eventInterp, Tcl_DStringValue(&command), + Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); + } else { + tclErr = Tcl_EvalEx(_eventInterp, data, actual, TCL_EVAL_GLOBAL); + } + } + ckfree(data); + } + } else { + /* + * The descriptor can not be coerced to a fileURL or UTF8 text. + */ + for (int i = 0; i < 4; i++) { + typeString[i] = ((char*)&initialType)[3-i]; + } + typeString[4] = '\0'; + sprintf(errString, "AEDoScriptHandler: invalid script type '%s', " + "must be coercable to 'furl' or 'utf8'", typeString); + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, errString, + strlen(errString)); + } + /* + * If we ran some Tcl code, put the result in the reply. + */ + if (tclErr >= 0) { + int reslen; + const char *result = + Tcl_GetStringFromObj(Tcl_GetObjResult(_eventInterp), &reslen); + if (tclErr == TCL_OK) { + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyDirectObject, typeChar, + result, reslen); + } else { + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, + result, reslen); + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorNumber, typeSInt32, + (Ptr) &tclErr,sizeof(int)); + } + } + return; +} +@end + +#pragma mark - + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXProcessFiles -- + * + * Extract a list of fileURLs from an AppleEvent and call the specified + * procedure with the file paths as arguments. + * + * Results: + * None. + * + * Side effects: + * The event is handled by running the procedure. + * + *---------------------------------------------------------------------- + */ + +static void +tkMacOSXProcessFiles( + NSAppleEventDescriptor* event, + NSAppleEventDescriptor* replyEvent, + Tcl_Interp *interp, + char* procedure) +{ + Tcl_Encoding utf8 = Tcl_GetEncoding(NULL, "utf-8"); + const AEDesc *fileSpecDesc = nil; + AEDesc contents; + char URLString[1 + URL_MAX_LENGTH]; + NSURL *fileURL; + DescType type; + Size actual; + long count, index; + AEKeyword keyword; + Tcl_DString command, pathName; + int code; + + /* + * Do nothing if we don't have an interpreter or the procedure doesn't exist. + */ + + if (!interp || !Tcl_FindCommand(interp, procedure, NULL, 0)) { + return; + } + + fileSpecDesc = [event aeDesc]; + if (fileSpecDesc == nil ) { + return; + } + + /* + * The AppleEvent's descriptor should either contain a value of + * typeObjectSpecifier or typeAEList. In the first case, the descriptor + * can be treated as a list of size 1 containing a value which can be + * coerced into a fileURL. In the second case we want to work with the list + * itself. Values in the list will be coerced into fileURL's if possible; + * otherwise they will be ignored. + */ + + /* Get a copy of the AppleEvent's descriptor. */ + AEGetParamDesc(fileSpecDesc, keyDirectObject, typeWildCard, &contents); + if (contents.descriptorType == typeAEList) { + fileSpecDesc = &contents; + } + + if (AECountItems(fileSpecDesc, &count) != noErr) { + AEDisposeDesc(&contents); + return; + } + + /* + * Construct a Tcl command which calls the procedure, passing the + * paths contained in the AppleEvent as arguments. + */ + + Tcl_DStringInit(&command); + Tcl_DStringAppend(&command, procedure, -1); + + for (index = 1; index <= count; index++) { + if (noErr != AEGetNthPtr(fileSpecDesc, index, typeFileURL, &keyword, + &type, (Ptr) URLString, URL_MAX_LENGTH, &actual)) { + continue; + } + if (type != typeFileURL) { + continue; + } + URLString[actual] = '\0'; + fileURL = [NSURL URLWithString:[NSString stringWithUTF8String:(char*)URLString]]; + if (fileURL == nil) { + continue; + } + Tcl_ExternalToUtfDString(utf8, [[fileURL path] UTF8String], -1, &pathName); + Tcl_DStringAppendElement(&command, Tcl_DStringValue(&pathName)); + Tcl_DStringFree(&pathName); + } + AEDisposeDesc(&contents); + + /* + * Handle the event by evaluating the Tcl expression we constructed. + */ + + code = Tcl_EvalEx(interp, Tcl_DStringValue(&command), + Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_BackgroundException(interp, code); + } + Tcl_DStringFree(&command); + return; +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXInitAppleEvents -- + * + * Register AppleEvent handlers with the NSAppleEventManager for + * this NSApplication. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkMacOSXInitAppleEvents( + Tcl_Interp *interp) /* not used */ +{ + NSAppleEventManager *aeManager = [NSAppleEventManager sharedAppleEventManager]; + static Boolean initialized = FALSE; + + if (!initialized) { + initialized = TRUE; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleQuitApplicationEvent:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEQuitApplication]; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleOpenApplicationEvent:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEOpenApplication]; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleReopenApplicationEvent:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEReopenApplication]; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleShowPreferencesEvent:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEShowPreferences]; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleOpenDocumentsEvent:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleOpenDocumentsEvent:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEPrintDocuments]; + + [aeManager setEventHandler:NSApp + andSelector:@selector(handleDoScriptEvent:withReplyEvent:) + forEventClass:kAEMiscStandards andEventID:kAEDoScript]; + } +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXDoHLEvent -- + * + * Dispatch an AppleEvent. + * + * Results: + * None. + * + * Side effects: + * Depend on the AppleEvent. + * + *---------------------------------------------------------------------- + */ + +int +TkMacOSXDoHLEvent( + void *theEvent) +{ + /* According to the NSAppleEventManager reference: + * "The theReply parameter always specifies a reply Apple event, never + * nil. However, the handler should not fill out the reply if the + * descriptor type for the reply event is typeNull, indicating the sender + * does not want a reply." + * The specified way to build such a non-nil descriptor is used here. But + * on OSX 10.11, the compiler nonetheless generates a warning. I am + * supressing the warning here -- maybe the warnings will stop in a future + * compiler release. + */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" +#endif + + NSAppleEventDescriptor* theReply = [NSAppleEventDescriptor nullDescriptor]; + NSAppleEventManager *aeManager = [NSAppleEventManager sharedAppleEventManager]; + + return [aeManager dispatchRawAppleEvent:(const AppleEvent*)theEvent + withRawReply: (AppleEvent *)theReply + handlerRefCon: (SRefCon)0]; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} + +/* + *---------------------------------------------------------------------- + * + * ReallyKillMe -- + * + * This procedure tries to kill the shell by running exit, called from + * an event scheduled by the "Quit" AppleEvent handler. + * + * Results: + * Runs the "exit" command which might kill the shell. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ReallyKillMe( + Tcl_Event *eventPtr, + int flags) +{ + Tcl_Interp *interp = ((KillEvent *) eventPtr)->interp; + int quit = Tcl_FindCommand(interp, "::tk::mac::Quit", NULL, 0)!=NULL; + int code = Tcl_EvalEx(interp, quit ? "::tk::mac::Quit" : "exit", -1, TCL_EVAL_GLOBAL); + + if (code != TCL_OK) { + /* + * Should be never reached... + */ + + Tcl_BackgroundException(interp, code); + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * MissedAnyParameters -- + * + * Checks to see if parameters are still left in the event. + * + * Results: + * True or false. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +MissedAnyParameters( + const AppleEvent *theEvent) +{ + DescType returnedType; + Size actualSize; + OSStatus err; + + err = AEGetAttributePtr(theEvent, keyMissedKeywordAttr, + typeWildCard, &returnedType, NULL, 0, &actualSize); + + return (err != errAEDescNotFound); +} + + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ |