1/*
2 * tkMacOSXHLEvents.c --
3 *
4 *	Implements high level event support for the Macintosh. Currently,
5 *	the only event that really does anything is the Quit event.
6 *
7 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
8 * Copyright 2001, Apple Computer, Inc.
9 * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
10 *
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 *
14 * RCS: @(#) $Id: tkMacOSXHLEvents.c,v 1.5.2.11 2007/06/29 03:22:02 das Exp $
15 */
16
17#include "tkMacOSXPrivate.h"
18
19/*
20 * This is a Tcl_Event structure that the Quit AppleEvent handler
21 * uses to schedule the ReallyKillMe function.
22 */
23
24typedef struct KillEvent {
25    Tcl_Event header;		/* Information that is standard for
26				 * all events. */
27    Tcl_Interp *interp;		/* Interp that was passed to the
28				 * Quit AppleEvent */
29} KillEvent;
30
31/*
32 * Static functions used only in this file.
33 */
34
35static OSErr QuitHandler(const AppleEvent * event, AppleEvent * reply,
36	long handlerRefcon);
37static OSErr OappHandler(const AppleEvent * event, AppleEvent * reply,
38	long handlerRefcon);
39static OSErr RappHandler(const AppleEvent * event, AppleEvent * reply,
40	long handlerRefcon);
41static OSErr OdocHandler(const AppleEvent * event, AppleEvent * reply,
42	long handlerRefcon);
43static OSErr PrintHandler(const AppleEvent * event, AppleEvent * reply,
44	long handlerRefcon);
45static OSErr ScriptHandler(const AppleEvent * event, AppleEvent * reply,
46	long handlerRefcon);
47static OSErr PrefsHandler(const AppleEvent * event, AppleEvent * reply,
48	long handlerRefcon);
49
50static int MissedAnyParameters(const AppleEvent *theEvent);
51static int ReallyKillMe(Tcl_Event *eventPtr, int flags);
52static OSStatus FSRefToDString(const FSRef *fsref, Tcl_DString *ds);
53
54/*
55 *----------------------------------------------------------------------
56 *
57 * TkMacOSXInitAppleEvents --
58 *
59 *	Initilize the Apple Events on the Macintosh. This registers the
60 *	core event handlers.
61 *
62 * Results:
63 *	None.
64 *
65 * Side effects:
66 *	None.
67 *
68 *----------------------------------------------------------------------
69 */
70
71void
72TkMacOSXInitAppleEvents(
73    Tcl_Interp *interp)		       /* Interp to handle basic events. */
74{
75    AEEventHandlerUPP	     OappHandlerUPP, RappHandlerUPP, OdocHandlerUPP,
76	    PrintHandlerUPP, QuitHandlerUPP, ScriptHandlerUPP, PrefsHandlerUPP;
77    static Boolean initialized = FALSE;
78
79    if (!initialized) {
80	initialized = TRUE;
81
82	/*
83	 * Install event handlers for the core apple events.
84	 */
85	QuitHandlerUPP = NewAEEventHandlerUPP(QuitHandler);
86	ChkErr(AEInstallEventHandler, kCoreEventClass, kAEQuitApplication,
87		QuitHandlerUPP, (long) interp, false);
88
89	OappHandlerUPP = NewAEEventHandlerUPP(OappHandler);
90	ChkErr(AEInstallEventHandler, kCoreEventClass, kAEOpenApplication,
91		OappHandlerUPP, (long) interp, false);
92
93	RappHandlerUPP = NewAEEventHandlerUPP(RappHandler);
94	ChkErr(AEInstallEventHandler, kCoreEventClass, kAEReopenApplication,
95		RappHandlerUPP, (long) interp, false);
96
97	OdocHandlerUPP = NewAEEventHandlerUPP(OdocHandler);
98	ChkErr(AEInstallEventHandler, kCoreEventClass, kAEOpenDocuments,
99		OdocHandlerUPP, (long) interp, false);
100
101	PrintHandlerUPP = NewAEEventHandlerUPP(PrintHandler);
102	ChkErr(AEInstallEventHandler, kCoreEventClass, kAEPrintDocuments,
103		PrintHandlerUPP, (long) interp, false);
104
105	PrefsHandlerUPP = NewAEEventHandlerUPP(PrefsHandler);
106	ChkErr(AEInstallEventHandler, kCoreEventClass, kAEShowPreferences,
107		PrefsHandlerUPP, (long) interp, false);
108
109	if (interp) {
110	    ScriptHandlerUPP = NewAEEventHandlerUPP(ScriptHandler);
111	    ChkErr(AEInstallEventHandler, kAEMiscStandards, kAEDoScript,
112		ScriptHandlerUPP, (long) interp, false);
113	}
114    }
115}
116
117/*
118 *----------------------------------------------------------------------
119 *
120 * TkMacOSXDoHLEvent --
121 *
122 *	Dispatch incomming highlevel events.
123 *
124 * Results:
125 *	None.
126 *
127 * Side effects:
128 *	Depends on the incoming event.
129 *
130 *----------------------------------------------------------------------
131 */
132
133int
134TkMacOSXDoHLEvent(
135    EventRecord *theEvent)
136{
137    return AEProcessAppleEvent(theEvent);
138}
139
140/*
141 *----------------------------------------------------------------------
142 *
143 * QuitHandler --
144 *
145 *	This is the 'quit' core Apple event handler.
146 *
147 * Results:
148 *	None.
149 *
150 * Side effects:
151 *	None.
152 *
153 *----------------------------------------------------------------------
154 */
155
156OSErr
157QuitHandler(
158    const AppleEvent * event,
159    AppleEvent * reply,
160    long handlerRefcon)
161{
162    Tcl_Interp *interp = (Tcl_Interp *) handlerRefcon;
163    KillEvent *eventPtr;
164
165    if (interp) {
166	/*
167	 * Call the exit command from the event loop, since you are not supposed
168	 * to call ExitToShell in an Apple Event Handler. We put this at the head
169	 * of Tcl's event queue because this message usually comes when the Mac is
170	 * shutting down, and we want to kill the shell as quickly as possible.
171	 */
172
173	eventPtr = (KillEvent *) ckalloc(sizeof(KillEvent));
174	eventPtr->header.proc = ReallyKillMe;
175	eventPtr->interp = interp;
176
177	Tcl_QueueEvent((Tcl_Event *) eventPtr, TCL_QUEUE_HEAD);
178    }
179    return noErr;
180}
181
182/*
183 *----------------------------------------------------------------------
184 *
185 * OappHandler --
186 *
187 *	This is the 'oapp' core Apple event handler.
188 *
189 * Results:
190 *	None.
191 *
192 * Side effects:
193 *	None.
194 *
195 *----------------------------------------------------------------------
196 */
197
198OSErr
199OappHandler(
200    const AppleEvent * event,
201    AppleEvent * reply,
202    long handlerRefcon)
203{
204    Tcl_CmdInfo dummy;
205    Tcl_Interp *interp = (Tcl_Interp *) handlerRefcon;
206
207    if (interp &&
208	    Tcl_GetCommandInfo(interp, "::tk::mac::OpenApplication", &dummy)) {
209	Tcl_GlobalEval(interp, "::tk::mac::OpenApplication");
210    }
211    return noErr;
212}
213
214/*
215 *----------------------------------------------------------------------
216 *
217 * RappHandler --
218 *
219 *	This is the 'rapp' core Apple event handler.
220 *
221 * Results:
222 *	None.
223 *
224 * Side effects:
225 *	None.
226 *
227 *----------------------------------------------------------------------
228 */
229
230OSErr
231RappHandler(
232    const AppleEvent * event,
233    AppleEvent * reply,
234    long handlerRefcon)
235{
236    Tcl_CmdInfo dummy;
237    Tcl_Interp *interp = (Tcl_Interp *) handlerRefcon;
238    ProcessSerialNumber thePSN = {0, kCurrentProcess};
239    OSStatus err = ChkErr(SetFrontProcess, &thePSN);
240
241    if (interp &&
242	    Tcl_GetCommandInfo(interp, "::tk::mac::ReopenApplication", &dummy)) {
243	Tcl_GlobalEval(interp, "::tk::mac::ReopenApplication");
244    }
245    return err;
246}
247
248/*
249 *----------------------------------------------------------------------
250 *
251 * PrefsHandler --
252 *
253 *	This is the 'pref' core Apple event handler.
254 *	Called when the user selects 'Preferences...' in MacOS X
255 *
256 * Results:
257 *	None.
258 *
259 * Side effects:
260 *	None.
261 *
262 *----------------------------------------------------------------------
263 */
264
265OSErr
266PrefsHandler(
267    const AppleEvent * event,
268    AppleEvent * reply,
269    long handlerRefcon)
270{
271    Tcl_CmdInfo dummy;
272    Tcl_Interp *interp = (Tcl_Interp *) handlerRefcon;
273
274    if (interp &&
275	    Tcl_GetCommandInfo(interp, "::tk::mac::ShowPreferences", &dummy)) {
276	Tcl_GlobalEval(interp, "::tk::mac::ShowPreferences");
277    }
278    return noErr;
279}
280
281/*
282 *----------------------------------------------------------------------
283 *
284 * OdocHandler --
285 *
286 *	This is the 'odoc' core Apple event handler.
287 *
288 * Results:
289 *	None.
290 *
291 * Side effects:
292 *	None.
293 *
294 *----------------------------------------------------------------------
295 */
296
297OSErr
298OdocHandler(
299    const AppleEvent * event,
300    AppleEvent * reply,
301    long handlerRefcon)
302{
303    Tcl_Interp *interp = (Tcl_Interp *) handlerRefcon;
304    AEDescList fileSpecList;
305    FSRef file;
306    OSStatus err;
307    DescType type;
308    Size actual;
309    long count;
310    AEKeyword keyword;
311    long index;
312    Tcl_DString command;
313    Tcl_DString pathName;
314    Tcl_CmdInfo dummy;
315
316    /*
317     * Don't bother if we don't have an interp or
318     * the open document procedure doesn't exist.
319     */
320
321    if ((interp == NULL) ||
322	    (Tcl_GetCommandInfo(interp, "::tk::mac::OpenDocument", &dummy)) == 0) {
323	    return noErr;
324    }
325
326    /*
327     * If we get any errors wil retrieving our parameters
328     * we just return with no error.
329     */
330
331    err = ChkErr(AEGetParamDesc, event, keyDirectObject, typeAEList,
332	    &fileSpecList);
333    if (err != noErr) {
334	return noErr;
335    }
336
337    err = MissedAnyParameters(event);
338    if (err != noErr) {
339	return noErr;
340    }
341
342    err = ChkErr(AECountItems, &fileSpecList, &count);
343    if (err != noErr) {
344	return noErr;
345    }
346
347    Tcl_DStringInit(&command);
348    Tcl_DStringAppend(&command, "::tk::mac::OpenDocument", -1);
349    for (index = 1; index <= count; index++) {
350	err = ChkErr(AEGetNthPtr, &fileSpecList, index, typeFSRef,
351		&keyword, &type, (Ptr) &file, sizeof(FSRef), &actual);
352	if ( err != noErr ) {
353	    continue;
354	}
355
356	err = ChkErr(FSRefToDString, &file, &pathName);
357	if (err == noErr) {
358	    Tcl_DStringAppendElement(&command, Tcl_DStringValue(&pathName));
359	    Tcl_DStringFree(&pathName);
360	}
361    }
362
363    Tcl_EvalEx(interp, Tcl_DStringValue(&command), Tcl_DStringLength(&command),
364	    TCL_EVAL_GLOBAL);
365
366    Tcl_DStringFree(&command);
367    return noErr;
368}
369
370/*
371 *----------------------------------------------------------------------
372 *
373 * PrintHandler --
374 *
375 *	This is the 'pdoc' core Apple event handler.
376 *
377 * Results:
378 *	None.
379 *
380 * Side effects:
381 *	None.
382 *
383 *----------------------------------------------------------------------
384 */
385
386OSErr
387PrintHandler(
388    const AppleEvent * event,
389    AppleEvent * reply,
390    long handlerRefcon)
391{
392    Tcl_Interp *interp = (Tcl_Interp *) handlerRefcon;
393    AEDescList fileSpecList;
394    FSRef file;
395    OSStatus err;
396    DescType type;
397    Size actual;
398    long count;
399    AEKeyword keyword;
400    long index;
401    Tcl_DString command;
402    Tcl_DString pathName;
403    Tcl_CmdInfo dummy;
404
405    /*
406     * Don't bother if we don't have an interp or
407     * the print document procedure doesn't exist.
408     */
409
410    if ((interp == NULL) ||
411	    (Tcl_GetCommandInfo(interp, "::tk::mac::PrintDocument", &dummy)) == 0) {
412	    return noErr;
413    }
414
415    /*
416     * If we get any errors wil retrieving our parameters
417     * we just return with no error.
418     */
419
420    err = ChkErr(AEGetParamDesc, event, keyDirectObject, typeAEList,
421	    &fileSpecList);
422    if (err != noErr) {
423	return noErr;
424    }
425
426    err = ChkErr(MissedAnyParameters, event);
427    if (err != noErr) {
428	return noErr;
429    }
430
431    err = ChkErr(AECountItems, &fileSpecList, &count);
432    if (err != noErr) {
433	return noErr;
434    }
435
436    Tcl_DStringInit(&command);
437    Tcl_DStringAppend(&command, "::tk::mac::PrintDocument", -1);
438    for (index = 1; index <= count; index++) {
439	err = ChkErr(AEGetNthPtr, &fileSpecList, index, typeFSRef, &keyword,
440		&type, (Ptr) &file, sizeof(FSRef), &actual);
441	if ( err != noErr ) {
442	    continue;
443	}
444
445	err = ChkErr(FSRefToDString, &file, &pathName);
446	if (err == noErr) {
447	    Tcl_DStringAppendElement(&command, Tcl_DStringValue(&pathName));
448	    Tcl_DStringFree(&pathName);
449	}
450    }
451
452    Tcl_EvalEx(interp, Tcl_DStringValue(&command), Tcl_DStringLength(&command),
453	    TCL_EVAL_GLOBAL);
454
455    Tcl_DStringFree(&command);
456    return noErr;
457}
458
459/*
460 *----------------------------------------------------------------------
461 *
462 * ScriptHandler --
463 *
464 *	This handler process the script event.
465 *
466 * Results:
467 *	Schedules the given event to be processed.
468 *
469 * Side effects:
470 *	None.
471 *
472 *----------------------------------------------------------------------
473 */
474
475OSErr
476ScriptHandler(
477    const AppleEvent * event,
478    AppleEvent * reply,
479    long handlerRefcon)
480{
481    OSStatus theErr;
482    AEDescList theDesc;
483    int tclErr = -1;
484    Tcl_Interp *interp;
485    char errString[128];
486
487    interp = (Tcl_Interp *) handlerRefcon;
488
489    /*
490     * The do script event receives one parameter that should be data or a file.
491     */
492    theErr = AEGetParamDesc(event, keyDirectObject, typeWildCard,
493	    &theDesc);
494    if (theErr != noErr) {
495	sprintf(errString, "AEDoScriptHandler: GetParamDesc error %d",
496		(int)theErr);
497	theErr = AEPutParamPtr(reply, keyErrorString, typeChar, errString,
498		strlen(errString));
499    } else if (MissedAnyParameters(event)) {
500	sprintf(errString, "AEDoScriptHandler: extra parameters");
501	AEPutParamPtr(reply, keyErrorString, typeChar, errString,
502		strlen(errString));
503	theErr = -1771;
504    } else {
505	Size size;
506
507	if (theDesc.descriptorType == (DescType)typeAlias &&
508		AEGetParamPtr(event, keyDirectObject, typeFSRef, NULL, NULL,
509		0, &size) == noErr && size == sizeof(FSRef)) {
510	    FSRef file;
511
512	    theErr = AEGetParamPtr(event, keyDirectObject, typeFSRef, NULL,
513		    &file, size, NULL);
514	    if (theErr == noErr) {
515		Tcl_DString scriptName;
516
517		theErr = FSRefToDString(&file, &scriptName);
518		if (theErr == noErr) {
519		    tclErr = Tcl_EvalFile(interp,
520			    Tcl_DStringValue(&scriptName));
521		    Tcl_DStringFree(&scriptName);
522		}
523	    } else {
524		sprintf(errString, "AEDoScriptHandler: file not found");
525		AEPutParamPtr(reply, keyErrorString, typeChar,
526			errString, strlen(errString));
527	    }
528	} else if (AEGetParamPtr(event, keyDirectObject, typeUTF8Text, NULL,
529		NULL, 0, &size) == noErr && size) {
530	    char *data = ckalloc(size + 1);
531
532	    theErr = AEGetParamPtr(event, keyDirectObject, typeUTF8Text, NULL,
533		    data, size, NULL);
534	    if (theErr == noErr) {
535		tclErr = Tcl_EvalEx(interp, data, size, TCL_EVAL_GLOBAL);
536	    }
537	} else {
538	    sprintf(errString,
539		    "AEDoScriptHandler: invalid script type '%-4.4s', "
540		    "must be 'alis' or coercable to 'utf8'",
541		    (char *)(&theDesc.descriptorType));
542	    AEPutParamPtr(reply, keyErrorString, typeChar,
543		    errString, strlen(errString));
544	    theErr = -1770;
545	}
546    }
547
548    /*
549     * If we actually go to run Tcl code - put the result in the reply.
550     */
551    if (tclErr >= 0) {
552	if (tclErr == TCL_OK)  {
553	    AEPutParamPtr(reply, keyDirectObject, typeChar,
554		Tcl_GetStringResult(interp),
555		strlen(Tcl_GetStringResult(interp)));
556	} else {
557	    AEPutParamPtr(reply, keyErrorString, typeChar,
558		Tcl_GetStringResult(interp),
559		strlen(Tcl_GetStringResult(interp)));
560	    AEPutParamPtr(reply, keyErrorNumber, typeSInt32,
561		(Ptr) &tclErr, sizeof(int));
562	}
563    }
564
565    AEDisposeDesc(&theDesc);
566
567    return theErr;
568}
569
570/*
571 *----------------------------------------------------------------------
572 *
573 * ReallyKillMe --
574 *
575 *	This proc tries to kill the shell by running exit,
576 *	called from an event scheduled by the "Quit" AppleEvent handler.
577 *
578 * Results:
579 *	Runs the "exit" command which might kill the shell.
580 *
581 * Side effects:
582 *	None.
583 *
584 *----------------------------------------------------------------------
585 */
586
587static int
588ReallyKillMe(
589    Tcl_Event *eventPtr,
590    int flags)
591{
592    Tcl_Interp *interp = ((KillEvent *) eventPtr)->interp;
593    Tcl_CmdInfo dummy;
594    if (Tcl_GetCommandInfo(interp, "::tk::mac::Quit", &dummy)) {
595	 Tcl_GlobalEval(interp, "::tk::mac::Quit");
596    } else {
597	Tcl_GlobalEval(interp, "exit");
598    }
599    return 1;
600}
601
602/*
603 *----------------------------------------------------------------------
604 *
605 * MissedAnyParameters --
606 *
607 *	Checks to see if parameters are still left in the event.
608 *
609 * Results:
610 *	True or false.
611 *
612 * Side effects:
613 *	None.
614 *
615 *----------------------------------------------------------------------
616 */
617
618int
619MissedAnyParameters(
620    const AppleEvent *theEvent)
621{
622   DescType returnedType;
623   Size actualSize;
624   OSStatus err;
625
626   err = AEGetAttributePtr(theEvent, keyMissedKeywordAttr,
627	    typeWildCard, &returnedType, NULL, 0, &actualSize);
628
629   return (err != errAEDescNotFound);
630}
631
632/*
633 *----------------------------------------------------------------------
634 *
635 * FSRefToDString --
636 *
637 *	Get a POSIX path from an FSRef.
638 *
639 * Results:
640 *	In the parameter ds.
641 *
642 * Side effects:
643 *	None.
644 *
645 *----------------------------------------------------------------------
646 */
647
648OSStatus
649FSRefToDString(
650    const FSRef *fsref,
651    Tcl_DString *ds)
652{
653    UInt8 fileName[PATH_MAX+1];
654    OSStatus err;
655
656    err = ChkErr(FSRefMakePath, fsref, fileName, sizeof(fileName));
657    if (err == noErr) {
658	Tcl_ExternalToUtfDString(NULL, (char*) fileName, -1, ds);
659    }
660    return err;
661}
662