summaryrefslogtreecommitdiffstats
path: root/tk8.6/macosx/tkMacOSXHLEvents.c
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-18 17:31:55 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-18 17:31:55 (GMT)
commit39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb (patch)
tree8e5374666c7f0b3017176ec9d6e6b6eae0dcabac /tk8.6/macosx/tkMacOSXHLEvents.c
parent066971b1e6e77991d9161bb0216a63ba94ea04f9 (diff)
parent6b095f3c8521ca7215e6ff5dcbada52b197ef7d0 (diff)
downloadblt-39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb.zip
blt-39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb.tar.gz
blt-39e34335fb6eb6eaf2b7ee51ccf172006dd46fbb.tar.bz2
Merge commit '6b095f3c8521ca7215e6ff5dcbada52b197ef7d0' as 'tk8.6'
Diffstat (limited to 'tk8.6/macosx/tkMacOSXHLEvents.c')
-rw-r--r--tk8.6/macosx/tkMacOSXHLEvents.c545
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:
+ */