Code

moving trunk for module inkscape
[inkscape.git] / packaging / macosx / ScriptExec / main.c
1 /*
2     Platypus - create MacOS X application bundles that execute scripts
3         This is the executable that goes into Platypus apps
4     Copyright (C) 2003 Sveinbjorn Thordarson <sveinbt@hi.is>
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20     main.c - main program file
22 */
24 ///////////////////////////////////////
25 // Includes
26 ///////////////////////////////////////    
27 #pragma mark Includes
29 // Apple stuff
30 #include <Carbon/Carbon.h>
31 #include <CoreFoundation/CoreFoundation.h>
32 #include <Security/Authorization.h>
33 #include <Security/AuthorizationTags.h>
35 // Unix stuff
36 #include <string.h>
37 #include <unistd.h>
38 #include <sys/wait.h>
39 #include <pthread.h>
41 ///////////////////////////////////////
42 // Definitions
43 ///////////////////////////////////////    
44 #pragma mark Definitions
46 // name length limits
47 #define kMaxPathLength 1024
49 // names of files bundled with app
50 #define kScriptFileName "script"
51 #define kOpenDocFileName "openDoc"
53 // custom carbon events
54 #define kEventClassRedFatalAlert 911
55 #define kEventKindX11Failed 911
56 #define kEventKindFCCacheFailed 912
58 //maximum arguments the script accepts 
59 #define kMaxArgumentsToScript 252
61 ///////////////////////////////////////
62 // Prototypes
63 ///////////////////////////////////////    
64 #pragma mark Prototypes
66 static void *Execute(void *arg);
67 static void *OpenDoc(void *arg);
68 static OSErr ExecuteScript(char *script, pid_t *pid);
70 static void  GetParameters(void);
71 static char* GetScript(void);
72 static char* GetOpenDoc(void);
74 OSErr LoadMenuBar(char *appName);
76 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize);
77 static void RedFatalAlert(Str255 errorString, Str255 expStr);
78 static short DoesFileExist(char *path);
79 static OSStatus FixFCCache(void);
81 static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
82                               AppleEvent *reply, long refCon);
83 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
84                                  AppleEvent *reply, long refCon);
85 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
86                                  AppleEvent *reply, long refCon);
87 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
88                                  EventRef theEvent, void *userData);
89 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall,
90                                  EventRef theEvent, void *userData);
91 ///////////////////////////////////////
92 // Globals
93 ///////////////////////////////////////    
94 #pragma mark Globals
96 // process id of forked process
97 pid_t pid = 0;
99 // thread id of threads that start scripts
100 pthread_t odtid = 0, tid = 0;
102 // indicator of whether the script has completed executing
103 short taskDone = true;
105 // execution parameters
106 char scriptPath[kMaxPathLength];
107 char openDocPath[kMaxPathLength];
109 //arguments to the script
110 char *arguments[kMaxArgumentsToScript+3];
111 char *fileArgs[kMaxArgumentsToScript];
112 short numArgs = 0;
114 extern char **environ;
116 #pragma mark -
118 ///////////////////////////////////////
119 // Program entrance point
120 ///////////////////////////////////////
121 int main(int argc, char* argv[])
123     OSErr err = noErr;
124     EventTypeSpec X11events = { kEventClassRedFatalAlert, kEventKindX11Failed };
125     EventTypeSpec FCCacheEvents = { kEventClassRedFatalAlert, kEventKindFCCacheFailed };
127     InitCursor();
129     //install Apple Event handlers
130     err += AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
131                                  NewAEEventHandlerUPP(AppQuitAEHandler),
132                                  0, false);
133     err += AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
134                                  NewAEEventHandlerUPP(AppOpenDocAEHandler),
135                                  0, false);
136     err += AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
137                                  NewAEEventHandlerUPP(AppOpenAppAEHandler),
138                                  0, false);
139     err += InstallEventHandler(GetApplicationEventTarget(),
140                                NewEventHandlerUPP(X11FailedHandler), 1,
141                                &X11events, NULL, NULL);
142     err += InstallEventHandler(GetApplicationEventTarget(),
143                                NewEventHandlerUPP(FCCacheFailedHandler), 1,
144                                &FCCacheEvents, NULL, NULL);
146     if (err) RedFatalAlert("\pInitialization Error",
147                            "\pError initing Apple Event handlers.");
149     //create the menu bar
150     if (err = LoadMenuBar(NULL)) RedFatalAlert("\pInitialization Error",
151                                                "\pError loading MenuBar.nib.");
152     
153     GetParameters(); //load data from files containing exec settings
155     RunApplicationEventLoop(); //Run the event loop
156     return 0;
158                                  
159 #pragma mark -
162 //////////////////////////////////
163 // Handler for when X11 fails to start
164 //////////////////////////////////
165 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall, 
166                                  EventRef theEvent, void *userData)
169     pthread_join(tid, NULL);
170     if (odtid) pthread_join(odtid, NULL);
171  
172         SInt16 itemHit;
174         AlertStdAlertParamRec params;
175         params.movable = true;
176         params.helpButton = false;
177         params.filterProc = NULL;
178         params.defaultText = "\pRun fc-cache";
179         params.cancelText = "\pIgnore";
180         params.otherText = NULL;
181         params.defaultButton = kAlertStdAlertOKButton;
182         params.cancelButton = kAlertStdAlertCancelButton;
183         params.position = kWindowDefaultPosition;
185         StandardAlert(kAlertStopAlert, "\pFont caches may need to be updated",
186                         "\pThere is a problem on OS X 10.4.x where X11 installation does not always generate the necessary fontconfig caches.  This can be corrected by running /usr/X11R6/bin/fc-cache as root.\n\nThis may take some time.  Please do not close Inkscape.",
187                         &params, &itemHit);
188     
189         if (itemHit == kAlertStdAlertOKButton)
190         {
191                 OSStatus err = FixFCCache();
193                 if (err == errAuthorizationSuccess)
194                 {
195                         params.defaultText = (void *) kAlertDefaultOKText;
196                         params.cancelText = NULL;
198                         StandardAlert(kAlertNoteAlert, "\pFont caches have been updated",
199                                         "\pPlease re-run Inkscape.", &params, &itemHit);
200                         system("test -d $HOME/.inkscape || mkdir $HOME/.inkscape; touch $HOME/.inkscape/.fccache");
201                 }
202         }
203         else
204         {
205                 params.defaultText = (void *) kAlertDefaultOKText;
206                 params.cancelText = NULL;
208                 StandardAlert(kAlertNoteAlert, "\pFont caches have not been updated",
209                                 "\pThey can be updated manually by running the following:\n   sudo /usr/X11R6/bin/fc-cache\nOnce you have dealt with this, please re-run Inkscape.", &params, &itemHit);
210                 system("test -d $HOME/.inkscape || mkdir $HOME/.inkscape; touch $HOME/.inkscape/.fccache");
211         }
212     
213         ExitToShell();
215     return noErr;
219 /////////////////////////////////////
220 // Code to run fc-cache on first run
221 /////////////////////////////////////
222 static OSStatus FixFCCache (void)
224    char* args[1];
226         // Run fc-cache
227         AuthorizationItem authItems[] = 
228         {
229         {
230                 kAuthorizationRightExecute,
231                 23,
232                 "/usr/X11R6/bin/fc-cache",
233                 0
234         }
235         };
236         AuthorizationItemSet authItemSet =
237         {
238                 1,
239                 authItems
240         };
241         AuthorizationRef authRef = NULL;
242         OSStatus err = AuthorizationCreate (NULL, &authItemSet,
243                         kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, &authRef);
245         if (err == errAuthorizationSuccess)
246         {
247                 //the arguments parameter to AuthorizationExecuteWithPrivileges is
248                 //a NULL terminated array of C string pointers.
249                 args[0]= NULL;
251                 AuthorizationExecuteWithPrivileges (authRef, "/usr/X11R6/bin/fc-cache", 
252                                 kAuthorizationFlagDefaults, args, NULL);
253         }
254     AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
256         return err;
259 ///////////////////////////////////
260 // Execution thread starts here
261 ///////////////////////////////////
262 static void *Execute (void *arg)
264     EventRef event;
265     
266     taskDone = false;
267     
268     OSErr err = ExecuteScript(scriptPath, &pid);
269     if (err == (OSErr)11) {
270         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindX11Failed, 0,
271                     kEventAttributeNone, &event);
272         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
273     }
274     else if (err == (OSErr)12) {
275         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindFCCacheFailed, 0,
276                     kEventAttributeNone, &event);
277         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
278     }
279     else ExitToShell();
280     return 0;
283 ///////////////////////////////////
284 // Open additional documents thread starts here
285 ///////////////////////////////////
286 static void *OpenDoc (void *arg)
288     ExecuteScript(openDocPath, NULL);
289     return 0;
292 ///////////////////////////////////////
293 // Run a script via the system command
294 ///////////////////////////////////////
295 static OSErr ExecuteScript (char *script, pid_t *pid)
297     pid_t wpid = 0, p = 0;
298     int status, i;
299  
300     if (! pid) pid = &p;
302     // Generate the array of argument strings before we do any executing
303     arguments[0] = script;
304     for (i = 0; i < numArgs; i++) arguments[i + 1] = fileArgs[i];
305     arguments[i + 1] = NULL;
307     *pid = fork(); //open fork
308     
309     if (*pid == (pid_t)-1) exit(13); //error
310     else if (*pid == 0) { //child process started
311         execve(arguments[0], arguments, environ);
312         exit(13); //if we reach this point, there's an error
313     }
315     wpid = waitpid(*pid, &status, 0); //wait while child process finishes
316     
317     if (wpid == (pid_t)-1) return wpid;
318     return (OSErr)WEXITSTATUS(status);
321 #pragma mark -
323 ///////////////////////////////////////
324 // This function loads all the neccesary settings
325 // from config files in the Resources folder
326 ///////////////////////////////////////
327 static void GetParameters (void)
329     char *str;
330     if (! (str = (char *)GetScript())) //get path to script to be executed
331         RedFatalAlert("\pInitialization Error",
332                       "\pError getting script from application bundle.");
333     strcpy((char *)&scriptPath, str);
334     
335     if (! (str = (char *)GetOpenDoc())) //get path to openDoc
336         RedFatalAlert("\pInitialization Error",
337                       "\pError getting openDoc from application bundle.");
338     strcpy((char *)&openDocPath, str);
341 ///////////////////////////////////////
342 // Get path to the script in Resources folder
343 ///////////////////////////////////////
344 static char* GetScript (void)
346     CFStringRef fileName;
347     CFBundleRef appBundle;
348     CFURLRef scriptFileURL;
349     FSRef fileRef;
350     FSSpec fileSpec;
351     char *path;
353     //get CF URL for script
354     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
355     if (! (fileName = CFStringCreateWithCString(NULL, kScriptFileName,
356                                                 kCFStringEncodingASCII)))
357         return NULL;
358     if (! (scriptFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
359                                                    NULL))) return NULL;
360     
361     //Get file reference from Core Foundation URL
362     if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL;
363     
364     //dispose of the CF variables
365     CFRelease(scriptFileURL);
366     CFRelease(fileName);
367     
368     //convert FSRef to FSSpec
369     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
370                          NULL)) return NULL;
371         
372     //create path string
373     if (! (path = malloc(kMaxPathLength))) return NULL;
374     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
375     if (! DoesFileExist(path)) return NULL;
376     
377     return path;
380 ///////////////////////////////////////
381 // Gets the path to openDoc in Resources folder
382 ///////////////////////////////////////
383 static char* GetOpenDoc (void)
385     CFStringRef fileName;
386     CFBundleRef appBundle;
387     CFURLRef openDocFileURL;
388     FSRef fileRef;
389     FSSpec fileSpec;
390     char *path;
391     
392     //get CF URL for openDoc
393     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
394     if (! (fileName = CFStringCreateWithCString(NULL, kOpenDocFileName,
395                                                 kCFStringEncodingASCII)))
396         return NULL;
397     if (! (openDocFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
398                                                     NULL))) return NULL;
399     
400     //Get file reference from Core Foundation URL
401     if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL;
402     
403     //dispose of the CF variables
404     CFRelease(openDocFileURL);
405     CFRelease(fileName);
406         
407     //convert FSRef to FSSpec
408     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
409                          NULL)) return NULL;
411     //create path string
412     if (! (path = malloc(kMaxPathLength))) return NULL;
413     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
414     if (! DoesFileExist(path)) return NULL;
415     
416     return path;
419 #pragma mark -
421 /////////////////////////////////////
422 // Load menu bar from nib
423 /////////////////////////////////////
424 OSErr LoadMenuBar (char *appName)
426     OSErr err;
427     IBNibRef nibRef;
428     
429     if (err = CreateNibReference(CFSTR("MenuBar"), &nibRef)) return err;
430     if (err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"))) return err;
431     DisposeNibReference(nibRef);
433     return noErr;
436 #pragma mark -
438 ///////////////////////////////////////
439 // Generate path string from FSSpec record
440 ///////////////////////////////////////
441 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize)
443     OSErr err = noErr;
444     FSRef fileRef;
446     //create file reference from file spec
447     if (err = FSpMakeFSRef(&file, &fileRef)) return err;
449     // and then convert the FSRef to a path
450     return FSRefMakePath(&fileRef, path, maxPathSize);
453 ////////////////////////////////////////
454 // Standard red error alert, then exit application
455 ////////////////////////////////////////
456 static void RedFatalAlert (Str255 errorString, Str255 expStr)
458     StandardAlert(kAlertStopAlert, errorString,  expStr, NULL, NULL);
459     ExitToShell();
462 ///////////////////////////////////////
463 // Determines whether file exists at path or not
464 ///////////////////////////////////////
465 static short DoesFileExist (char *path)
467     if (access(path, F_OK) == -1) return false;
468     return true;        
471 #pragma mark -
473 ///////////////////////////////////////
474 // Apple Event handler for Quit i.e. from
475 // the dock or Application menu item
476 ///////////////////////////////////////
477 static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
478                               AppleEvent *reply, long refCon)
480     #pragma unused (reply, refCon, theAppleEvent)
482     while (numArgs > 0) free(fileArgs[numArgs--]);
483     
484     if (! taskDone && pid) { //kill the script process brutally
485         kill(pid, 9);
486         printf("Platypus App: PID %d killed brutally\n", pid);
487     }
488     
489     pthread_cancel(tid);
490     if (odtid) pthread_cancel(odtid);
491     
492     ExitToShell();
493     
494     return noErr;
497 /////////////////////////////////////
498 // Handler for docs dragged on app icon
499 /////////////////////////////////////
500 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
501                                  AppleEvent *reply, long refCon)
503     #pragma unused (reply, refCon)
504         
505     OSErr err = noErr;
506     AEDescList fileSpecList;
507     AEKeyword keyword;
508     DescType type;
509         
510     short i;
511     long count, actualSize;
512         
513     FSSpec fileSpec;
514     char path[kMaxPathLength];
515     
516     while (numArgs > 0) free(fileArgs[numArgs--]);
517         
518     //Read the AppleEvent
519     err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
520                          &fileSpecList);
521                 
522     err = AECountItems(&fileSpecList, &count); //Count number of files
523                 
524     for (i = 1; i <= count; i++) { //iteratively process each file
525         //get fsspec from apple event
526         if (! (err = AEGetNthPtr(&fileSpecList, i, typeFSS, &keyword, &type,
527                                  (Ptr)&fileSpec, sizeof(FSSpec), &actualSize)))
528         {
529             //get path from file spec
530             if ((err = FSMakePath(fileSpec, (unsigned char *)&path,
531                                   kMaxPathLength))) return err;
532                             
533             if (numArgs == kMaxArgumentsToScript) break;
535             if (! (fileArgs[numArgs] = malloc(kMaxPathLength))) return true;
537             strcpy(fileArgs[numArgs++], (char *)&path);
538         }
539         else return err;
540     }
541         
542     if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL);
543     else pthread_create(&tid, NULL, Execute, NULL);
544         
545     return err;
548 ///////////////////////////////
549 // Handler for clicking on app icon
550 ///////////////////////////////
551 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
552                                  AppleEvent *reply, long refCon)
554     #pragma unused (reply, refCon, theAppleEvent)
555         
556     // the app has been opened without any items dragged on to it
557     pthread_create(&tid, NULL, Execute, NULL);
559     return noErr;
563 static void OpenURL(Str255 url)
565         // Use Internet Config to hand the URL to the appropriate application, as
566         // set by the user in the Internet Preferences pane.
567         ICInstance icInstance;
568         // Applications creator code:
569         OSType signature = 'Inks';
570         OSStatus error = ICStart( &icInstance, signature );
571         if ( error == noErr )
572         {
573                 ConstStr255Param hint = 0x0;
574                 const char* data = url;
575                 long length = strlen(url);
576                 long start =  0;
577                 long end = length;
578                 // Don't bother testing return value (error); launched application will
579                 // report problems.
580                 ICLaunchURL( icInstance, hint, data, length, &start, &end );
581                 ICStop( icInstance );
582         }
586 //////////////////////////////////
587 // Handler for when X11 fails to start
588 //////////////////////////////////
589 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall, 
590                                  EventRef theEvent, void *userData)
592     #pragma unused(theHanderCall, theEvent, userData)
594     pthread_join(tid, NULL);
595     if (odtid) pthread_join(odtid, NULL);
596  
597         SInt16 itemHit;
598         const char *getX11 = "\pGet X11 for Panther";
600         AlertStdAlertParamRec params;
601         params.movable = true;
602         params.helpButton = false;
603         params.filterProc = NULL;
604         params.defaultText = (StringPtr) kAlertDefaultOKText;
605         params.cancelText = getX11;
606         params.otherText = NULL;
607         params.defaultButton = kAlertStdAlertOKButton;
608         params.cancelButton = kAlertStdAlertCancelButton;
609         params.position = kWindowDefaultPosition;
611         StandardAlert(kAlertStopAlert, "\pFailed to start X11",
612                         "\pInkscape.app requires Apple's X11, which is freely downloadable from Apple's website for Panther (10.3.x) users and available as an optional install from the installation DVD for Tiger (10.4.x) users.\n\nPlease install X11 and restart Inkscape.",
613                         &params, &itemHit);
614     
615         if (itemHit == kAlertStdAlertCancelButton)
616         {
617                 OpenURL("http://www.apple.com/downloads/macosx/apple/x11formacosx.html");
618         }
620     ExitToShell();
623     return noErr;