Code

95092ee349a73c5505cafe20ad366ab7af284f92
[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>
40 #include <stdio.h>
42 ///////////////////////////////////////
43 // Definitions
44 ///////////////////////////////////////    
45 #pragma mark Definitions
47 // name length limits
48 #define kMaxPathLength 1024
50 // names of files bundled with app
51 #define kScriptFileName "script"
52 #define kOpenDocFileName "openDoc"
54 // custom carbon events
55 #define kEventClassRedFatalAlert 911
56 #define kEventKindX11Failed 911
57 #define kEventKindFCCacheFailed 912
59 //maximum arguments the script accepts 
60 #define kMaxArgumentsToScript 252
62 ///////////////////////////////////////
63 // Prototypes
64 ///////////////////////////////////////    
65 #pragma mark Prototypes
67 static void *Execute(void *arg);
68 static void *OpenDoc(void *arg);
69 static OSErr ExecuteScript(char *script, pid_t *pid);
71 static void  GetParameters(void);
72 static char* GetScript(void);
73 static char* GetOpenDoc(void);
75 OSErr LoadMenuBar(char *appName);
77 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize);
78 static void RedFatalAlert(Str255 errorString, Str255 expStr);
79 static short DoesFileExist(char *path);
80 static OSStatus FixFCCache(void);
82 static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
83                               AppleEvent *reply, long refCon);
84 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
85                                  AppleEvent *reply, long refCon);
86 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
87                                  AppleEvent *reply, long refCon);
88 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
89                                  EventRef theEvent, void *userData);
90 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall,
91                                  EventRef theEvent, void *userData);
92 ///////////////////////////////////////
93 // Globals
94 ///////////////////////////////////////    
95 #pragma mark Globals
97 // process id of forked process
98 pid_t pid = 0;
100 // thread id of threads that start scripts
101 pthread_t odtid = 0, tid = 0;
103 // indicator of whether the script has completed executing
104 short taskDone = true;
106 // execution parameters
107 char scriptPath[kMaxPathLength];
108 char openDocPath[kMaxPathLength];
110 //arguments to the script
111 char *arguments[kMaxArgumentsToScript+3];
112 char *fileArgs[kMaxArgumentsToScript];
113 short numArgs = 0;
115 extern char **environ;
117 #pragma mark -
119 ///////////////////////////////////////
120 // Program entrance point
121 ///////////////////////////////////////
122 int main(int argc, char* argv[])
124     OSErr err = noErr;
125     EventTypeSpec X11events = { kEventClassRedFatalAlert, kEventKindX11Failed };
126     EventTypeSpec FCCacheEvents = { kEventClassRedFatalAlert, kEventKindFCCacheFailed };
128     InitCursor();
130     //install Apple Event handlers
131     err += AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
132                                  NewAEEventHandlerUPP(AppQuitAEHandler),
133                                  0, false);
134     err += AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
135                                  NewAEEventHandlerUPP(AppOpenDocAEHandler),
136                                  0, false);
137     err += AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
138                                  NewAEEventHandlerUPP(AppOpenAppAEHandler),
139                                  0, false);
140     err += InstallEventHandler(GetApplicationEventTarget(),
141                                NewEventHandlerUPP(X11FailedHandler), 1,
142                                &X11events, NULL, NULL);
143     err += InstallEventHandler(GetApplicationEventTarget(),
144                                NewEventHandlerUPP(FCCacheFailedHandler), 1,
145                                &FCCacheEvents, NULL, NULL);
147     if (err) RedFatalAlert("\pInitialization Error",
148                            "\pError initing Apple Event handlers.");
150     //create the menu bar
151     if (err = LoadMenuBar(NULL)) RedFatalAlert("\pInitialization Error",
152                                                "\pError loading MenuBar.nib.");
153     
154     GetParameters(); //load data from files containing exec settings
156     RunApplicationEventLoop(); //Run the event loop
157     return 0;
159                                  
160 #pragma mark -
163 //////////////////////////////////
164 // Handler for when X11 fails to start
165 //////////////////////////////////
166 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall, 
167                                  EventRef theEvent, void *userData)
170     pthread_join(tid, NULL);
171     if (odtid) pthread_join(odtid, NULL);
172  
173         SInt16 itemHit;
175         AlertStdAlertParamRec params;
176         params.movable = true;
177         params.helpButton = false;
178         params.filterProc = NULL;
179         params.defaultText = "\pRun fc-cache";
180         params.cancelText = "\pIgnore";
181         params.otherText = NULL;
182         params.defaultButton = kAlertStdAlertOKButton;
183         params.cancelButton = kAlertStdAlertCancelButton;
184         params.position = kWindowDefaultPosition;
186         StandardAlert(kAlertStopAlert, "\pFont caches may need to be updated",
187                         "\pA problem occurs on OS X 10.4 where X11 does not always generate the necessary fontconfig caches.  This can be corrected by running fc-cache as root.\n\nThis can take several minutes, with high processor usage.  Please do not close Inkscape.",
188                         &params, &itemHit);
189     
190         if (itemHit == kAlertStdAlertOKButton)
191         {
192                 OSStatus err = FixFCCache();
194                 if (err == errAuthorizationSuccess)
195                 {
196                         params.defaultText = (void *) kAlertDefaultOKText;
197                         params.cancelText = NULL;
199                         StandardAlert(kAlertNoteAlert, "\pFont caches have been updated",
200                                         "\pPlease re-run Inkscape.", &params, &itemHit);
201                         system("test -d $HOME/.inkscape || mkdir $HOME/.inkscape; touch $HOME/.inkscape/.fccache");
202                 }
203         }
204         else
205         {
206                 params.defaultText = (void *) kAlertDefaultOKText;
207                 params.cancelText = NULL;
209                 StandardAlert(kAlertNoteAlert, "\pFont caches have not been updated",
210                                 "\pThey can be updated manually by running the following:\n   sudo /usr/X11R6/bin/fc-cache -f\nOnce you have dealt with this, please re-run Inkscape.", &params, &itemHit);
211                 system("test -d $HOME/.inkscape || mkdir $HOME/.inkscape; touch $HOME/.inkscape/.fccache");
212         }
213     
214         ExitToShell();
216     return noErr;
220 static size_t safeRead(int d, void *buf, size_t nbytes)
222         ssize_t bytesToRead = nbytes;
223         ssize_t bytesRead = 0;
224         char *offset = (char *) buf;
226         while ((bytesToRead > 0))
227         {
228                 bytesRead = read(d, offset, bytesToRead);
229                 if (bytesRead > 0)
230                 {
231                         offset += bytesRead;
232                         bytesToRead -= bytesRead;
233                 }
234                 else if (bytesRead == 0)
235                 {       
236                         // Reached EOF.
237                         break;
238                 }
239                 else if (bytesRead == -1)
240                 {
241                         if ((errno == EINTR) || (errno == EAGAIN))
242                         {
243                                 // Try again.
244                                 continue;
245                         }
246                         return 0;
247                 }
248         }
249         return bytesRead;
253 /////////////////////////////////////
254 // Code to run fc-cache on first run
255 /////////////////////////////////////
256 static OSStatus FixFCCache (void)
258         FILE *fileConnToChild = NULL;
259         int fdConnToChild = 0;
260         pid_t childPID = WAIT_ANY;
261         size_t bytesChildPID;
262         size_t bytesRead;
263         int status;
265         char commandStr[] = "/usr/X11R6/bin/fc-cache";
266         char *commandArgs[] = { "-f", NULL };
268         // Run fc-cache
269         AuthorizationItem authItems[] = 
270         {
271         {
272                 kAuthorizationRightExecute,
273                 strlen(commandStr),
274                 commandStr,
275                 0
276         }
277         };
278         AuthorizationItemSet authItemSet =
279         {
280                 1,
281                 authItems
282         };
283         AuthorizationRef authRef = NULL;
284         OSStatus err = AuthorizationCreate (NULL, &authItemSet,
285                         kAuthorizationFlagInteractionAllowed | 
286                         kAuthorizationFlagExtendRights, &authRef);
288         if (err == errAuthorizationSuccess)
289         {
290                 err = AuthorizationExecuteWithPrivileges(authRef, commandStr, 
291                                 kAuthorizationFlagDefaults, commandArgs,
292                                 &fileConnToChild);
294                 if (err == errAuthorizationSuccess)
295                 {
296                         // Unfortunately, AuthorizationExecuteWithPrivileges
297                         // does not return the process ID associated with the
298                         // process it runs.  The best solution we have it to
299                         // try and get the process ID from the file descriptor.
300                         // This is based on example code from Apple's
301                         // MoreAuthSample.
303                         fdConnToChild = fileno(fileConnToChild);
304                         
305                         // Try an get the process ID of the fc-cache command
306                         bytesChildPID = sizeof(childPID);
307                         bytesRead = safeRead(fdConnToChild, &childPID,
308                                         bytesChildPID);
309                         if (bytesRead != bytesChildPID)
310                         {
311                                 // If we can't get it the best alternative
312                                 // is to wait for any child to finish.
313                                 childPID = WAIT_ANY;
314                         }
316                         if (fileConnToChild != NULL) {
317                                 fclose(fileConnToChild);
318                         }
320                         // Wait for child process to finish.
321                         waitpid(childPID, &status, 0);
322                 }
323         }
324         AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
326         return err;
329 ///////////////////////////////////
330 // Execution thread starts here
331 ///////////////////////////////////
332 static void *Execute (void *arg)
334     EventRef event;
335     
336     taskDone = false;
337     
338     OSErr err = ExecuteScript(scriptPath, &pid);
339     if (err == (OSErr)11) {
340         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindX11Failed, 0,
341                     kEventAttributeNone, &event);
342         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
343     }
344     else if (err == (OSErr)12) {
345         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindFCCacheFailed, 0,
346                     kEventAttributeNone, &event);
347         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
348     }
349     else ExitToShell();
350     return 0;
353 ///////////////////////////////////
354 // Open additional documents thread starts here
355 ///////////////////////////////////
356 static void *OpenDoc (void *arg)
358     ExecuteScript(openDocPath, NULL);
359     return 0;
362 ///////////////////////////////////////
363 // Run a script via the system command
364 ///////////////////////////////////////
365 static OSErr ExecuteScript (char *script, pid_t *pid)
367     pid_t wpid = 0, p = 0;
368     int status, i;
369  
370     if (! pid) pid = &p;
372     // Generate the array of argument strings before we do any executing
373     arguments[0] = script;
374     for (i = 0; i < numArgs; i++) arguments[i + 1] = fileArgs[i];
375     arguments[i + 1] = NULL;
377     *pid = fork(); //open fork
378     
379     if (*pid == (pid_t)-1) exit(13); //error
380     else if (*pid == 0) { //child process started
381         execve(arguments[0], arguments, environ);
382         exit(13); //if we reach this point, there's an error
383     }
385     wpid = waitpid(*pid, &status, 0); //wait while child process finishes
386     
387     if (wpid == (pid_t)-1) return wpid;
388     return (OSErr)WEXITSTATUS(status);
391 #pragma mark -
393 ///////////////////////////////////////
394 // This function loads all the neccesary settings
395 // from config files in the Resources folder
396 ///////////////////////////////////////
397 static void GetParameters (void)
399     char *str;
400     if (! (str = (char *)GetScript())) //get path to script to be executed
401         RedFatalAlert("\pInitialization Error",
402                       "\pError getting script from application bundle.");
403     strcpy((char *)&scriptPath, str);
404     
405     if (! (str = (char *)GetOpenDoc())) //get path to openDoc
406         RedFatalAlert("\pInitialization Error",
407                       "\pError getting openDoc from application bundle.");
408     strcpy((char *)&openDocPath, str);
411 ///////////////////////////////////////
412 // Get path to the script in Resources folder
413 ///////////////////////////////////////
414 static char* GetScript (void)
416     CFStringRef fileName;
417     CFBundleRef appBundle;
418     CFURLRef scriptFileURL;
419     FSRef fileRef;
420     FSSpec fileSpec;
421     char *path;
423     //get CF URL for script
424     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
425     if (! (fileName = CFStringCreateWithCString(NULL, kScriptFileName,
426                                                 kCFStringEncodingASCII)))
427         return NULL;
428     if (! (scriptFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
429                                                    NULL))) return NULL;
430     
431     //Get file reference from Core Foundation URL
432     if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL;
433     
434     //dispose of the CF variables
435     CFRelease(scriptFileURL);
436     CFRelease(fileName);
437     
438     //convert FSRef to FSSpec
439     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
440                          NULL)) return NULL;
441         
442     //create path string
443     if (! (path = malloc(kMaxPathLength))) return NULL;
444     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
445     if (! DoesFileExist(path)) return NULL;
446     
447     return path;
450 ///////////////////////////////////////
451 // Gets the path to openDoc in Resources folder
452 ///////////////////////////////////////
453 static char* GetOpenDoc (void)
455     CFStringRef fileName;
456     CFBundleRef appBundle;
457     CFURLRef openDocFileURL;
458     FSRef fileRef;
459     FSSpec fileSpec;
460     char *path;
461     
462     //get CF URL for openDoc
463     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
464     if (! (fileName = CFStringCreateWithCString(NULL, kOpenDocFileName,
465                                                 kCFStringEncodingASCII)))
466         return NULL;
467     if (! (openDocFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
468                                                     NULL))) return NULL;
469     
470     //Get file reference from Core Foundation URL
471     if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL;
472     
473     //dispose of the CF variables
474     CFRelease(openDocFileURL);
475     CFRelease(fileName);
476         
477     //convert FSRef to FSSpec
478     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
479                          NULL)) return NULL;
481     //create path string
482     if (! (path = malloc(kMaxPathLength))) return NULL;
483     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
484     if (! DoesFileExist(path)) return NULL;
485     
486     return path;
489 #pragma mark -
491 /////////////////////////////////////
492 // Load menu bar from nib
493 /////////////////////////////////////
494 OSErr LoadMenuBar (char *appName)
496     OSErr err;
497     IBNibRef nibRef;
498     
499     if (err = CreateNibReference(CFSTR("MenuBar"), &nibRef)) return err;
500     if (err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"))) return err;
501     DisposeNibReference(nibRef);
503     return noErr;
506 #pragma mark -
508 ///////////////////////////////////////
509 // Generate path string from FSSpec record
510 ///////////////////////////////////////
511 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize)
513     OSErr err = noErr;
514     FSRef fileRef;
516     //create file reference from file spec
517     if (err = FSpMakeFSRef(&file, &fileRef)) return err;
519     // and then convert the FSRef to a path
520     return FSRefMakePath(&fileRef, path, maxPathSize);
523 ////////////////////////////////////////
524 // Standard red error alert, then exit application
525 ////////////////////////////////////////
526 static void RedFatalAlert (Str255 errorString, Str255 expStr)
528     StandardAlert(kAlertStopAlert, errorString,  expStr, NULL, NULL);
529     ExitToShell();
532 ///////////////////////////////////////
533 // Determines whether file exists at path or not
534 ///////////////////////////////////////
535 static short DoesFileExist (char *path)
537     if (access(path, F_OK) == -1) return false;
538     return true;        
541 #pragma mark -
543 ///////////////////////////////////////
544 // Apple Event handler for Quit i.e. from
545 // the dock or Application menu item
546 ///////////////////////////////////////
547 static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
548                               AppleEvent *reply, long refCon)
550     #pragma unused (reply, refCon, theAppleEvent)
552     while (numArgs > 0) free(fileArgs[numArgs--]);
553     
554     if (! taskDone && pid) { //kill the script process brutally
555         kill(pid, 9);
556         printf("Platypus App: PID %d killed brutally\n", pid);
557     }
558     
559     pthread_cancel(tid);
560     if (odtid) pthread_cancel(odtid);
561     
562     ExitToShell();
563     
564     return noErr;
567 /////////////////////////////////////
568 // Handler for docs dragged on app icon
569 /////////////////////////////////////
570 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
571                                  AppleEvent *reply, long refCon)
573     #pragma unused (reply, refCon)
574         
575     OSErr err = noErr;
576     AEDescList fileSpecList;
577     AEKeyword keyword;
578     DescType type;
579         
580     short i;
581     long count, actualSize;
582         
583     FSSpec fileSpec;
584     char path[kMaxPathLength];
585     
586     while (numArgs > 0) free(fileArgs[numArgs--]);
587         
588     //Read the AppleEvent
589     err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
590                          &fileSpecList);
591                 
592     err = AECountItems(&fileSpecList, &count); //Count number of files
593                 
594     for (i = 1; i <= count; i++) { //iteratively process each file
595         //get fsspec from apple event
596         if (! (err = AEGetNthPtr(&fileSpecList, i, typeFSS, &keyword, &type,
597                                  (Ptr)&fileSpec, sizeof(FSSpec), &actualSize)))
598         {
599             //get path from file spec
600             if ((err = FSMakePath(fileSpec, (unsigned char *)&path,
601                                   kMaxPathLength))) return err;
602                             
603             if (numArgs == kMaxArgumentsToScript) break;
605             if (! (fileArgs[numArgs] = malloc(kMaxPathLength))) return true;
607             strcpy(fileArgs[numArgs++], (char *)&path);
608         }
609         else return err;
610     }
611         
612     if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL);
613     else pthread_create(&tid, NULL, Execute, NULL);
614         
615     return err;
618 ///////////////////////////////
619 // Handler for clicking on app icon
620 ///////////////////////////////
621 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
622                                  AppleEvent *reply, long refCon)
624     #pragma unused (reply, refCon, theAppleEvent)
625         
626     // the app has been opened without any items dragged on to it
627     pthread_create(&tid, NULL, Execute, NULL);
629     return noErr;
633 static void OpenURL(Str255 url)
635         // Use Internet Config to hand the URL to the appropriate application, as
636         // set by the user in the Internet Preferences pane.
637         ICInstance icInstance;
638         // Applications creator code:
639         OSType signature = 'Inks';
640         OSStatus error = ICStart( &icInstance, signature );
641         if ( error == noErr )
642         {
643                 ConstStr255Param hint = 0x0;
644                 const char* data = url;
645                 long length = strlen(url);
646                 long start =  0;
647                 long end = length;
648                 // Don't bother testing return value (error); launched application will
649                 // report problems.
650                 ICLaunchURL( icInstance, hint, data, length, &start, &end );
651                 ICStop( icInstance );
652         }
656 //////////////////////////////////
657 // Handler for when X11 fails to start
658 //////////////////////////////////
659 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall, 
660                                  EventRef theEvent, void *userData)
662     #pragma unused(theHanderCall, theEvent, userData)
664     pthread_join(tid, NULL);
665     if (odtid) pthread_join(odtid, NULL);
666  
667         SInt16 itemHit;
668         const char *getX11 = "\pGet X11 for Panther";
670         AlertStdAlertParamRec params;
671         params.movable = true;
672         params.helpButton = false;
673         params.filterProc = NULL;
674         params.defaultText = (StringPtr) kAlertDefaultOKText;
675         params.cancelText = getX11;
676         params.otherText = NULL;
677         params.defaultButton = kAlertStdAlertOKButton;
678         params.cancelButton = kAlertStdAlertCancelButton;
679         params.position = kWindowDefaultPosition;
681         StandardAlert(kAlertStopAlert, "\pFailed to start X11",
682                         "\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.",
683                         &params, &itemHit);
684     
685         if (itemHit == kAlertStdAlertCancelButton)
686         {
687                 OpenURL("http://www.apple.com/downloads/macosx/apple/x11formacosx.html");
688         }
690     ExitToShell();
693     return noErr;