Code

* packaging/macosx/ScriptExec/main.c: Make sure the OS X application
[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 static void RequestUserAttention(void)
165     NMRecPtr notificationRequest = (NMRecPtr) NewPtr(sizeof(NMRec));
167     memset(notificationRequest, 0, sizeof(*notificationRequest));
168     notificationRequest->qType = nmType;
169     notificationRequest->nmMark = 1;
170     notificationRequest->nmIcon = 0;
171     notificationRequest->nmSound = 0;
172     notificationRequest->nmStr = NULL;
173     notificationRequest->nmResp = NULL;
175     verify_noerr(NMInstall(notificationRequest));
179 static void ShowFirstStartWarningDialog(void)
181     SInt16 itemHit;
183     AlertStdAlertParamRec params;
184     params.movable = true;
185     params.helpButton = false;
186     params.filterProc = NULL;
187     params.defaultText = (void *) kAlertDefaultOKText;
188     params.cancelText = NULL;
189     params.otherText = NULL;
190     params.defaultButton = kAlertStdAlertOKButton;
191     params.cancelButton = kAlertStdAlertCancelButton;
192     params.position = kWindowDefaultPosition;
194     StandardAlert(kAlertNoteAlert, "\pInkscape on Mac OS X",
195             "\pWhile Inkscape is open, its windows can be displayed or hidden by displaying or hiding the X11 application.\n\nThe first time this version of Inkscape is run it may take several minutes before the main window is displayed while font caches are built.",
196             &params, &itemHit);
200 //////////////////////////////////
201 // Handler for when fontconfig caches need to be generated
202 //////////////////////////////////
203 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall, 
204                                  EventRef theEvent, void *userData)
207     pthread_join(tid, NULL);
208     if (odtid) pthread_join(odtid, NULL);
210     // Bounce Inkscape Dock icon
211     RequestUserAttention();
212     // Need to show warning to the user, then carry on.
213     ShowFirstStartWarningDialog();
215     // Note that we've seen the warning.
216     system("test -d \"$HOME/.inkscape\" || mkdir \"$HOME/.inkscape\"; "
217            "touch \"$HOME/.inkscape/.fccache-new\"");
218     // Rerun now.
219     OSErr err = ExecuteScript(scriptPath, &pid);
220     ExitToShell();
222     return noErr;
226 static size_t safeRead(int d, void *buf, size_t nbytes)
228         ssize_t bytesToRead = nbytes;
229         ssize_t bytesRead = 0;
230         char *offset = (char *) buf;
232         while ((bytesToRead > 0))
233         {
234                 bytesRead = read(d, offset, bytesToRead);
235                 if (bytesRead > 0)
236                 {
237                         offset += bytesRead;
238                         bytesToRead -= bytesRead;
239                 }
240                 else if (bytesRead == 0)
241                 {       
242                         // Reached EOF.
243                         break;
244                 }
245                 else if (bytesRead == -1)
246                 {
247                         if ((errno == EINTR) || (errno == EAGAIN))
248                         {
249                                 // Try again.
250                                 continue;
251                         }
252                         return 0;
253                 }
254         }
255         return bytesRead;
259 /////////////////////////////////////
260 // Code to run fc-cache on first run
261 /////////////////////////////////////
262 static OSStatus FixFCCache (void)
264         FILE *fileConnToChild = NULL;
265         int fdConnToChild = 0;
266         pid_t childPID = WAIT_ANY;
267         size_t bytesChildPID;
268         size_t bytesRead;
269         int status;
271         char commandStr[] = "/usr/X11R6/bin/fc-cache";
272         char *commandArgs[] = { "-f", NULL };
274         // Run fc-cache
275         AuthorizationItem authItems[] = 
276         {
277         {
278                 kAuthorizationRightExecute,
279                 strlen(commandStr),
280                 commandStr,
281                 0
282         }
283         };
284         AuthorizationItemSet authItemSet =
285         {
286                 1,
287                 authItems
288         };
289         AuthorizationRef authRef = NULL;
290         OSStatus err = AuthorizationCreate (NULL, &authItemSet,
291                         kAuthorizationFlagInteractionAllowed | 
292                         kAuthorizationFlagExtendRights, &authRef);
294         if (err == errAuthorizationSuccess)
295         {
296                 err = AuthorizationExecuteWithPrivileges(authRef, commandStr, 
297                                 kAuthorizationFlagDefaults, commandArgs,
298                                 &fileConnToChild);
300                 if (err == errAuthorizationSuccess)
301                 {
302                         // Unfortunately, AuthorizationExecuteWithPrivileges
303                         // does not return the process ID associated with the
304                         // process it runs.  The best solution we have it to
305                         // try and get the process ID from the file descriptor.
306                         // This is based on example code from Apple's
307                         // MoreAuthSample.
309                         fdConnToChild = fileno(fileConnToChild);
310                         
311                         // Try an get the process ID of the fc-cache command
312                         bytesChildPID = sizeof(childPID);
313                         bytesRead = safeRead(fdConnToChild, &childPID,
314                                         bytesChildPID);
315                         if (bytesRead != bytesChildPID)
316                         {
317                                 // If we can't get it the best alternative
318                                 // is to wait for any child to finish.
319                                 childPID = WAIT_ANY;
320                         }
322                         if (fileConnToChild != NULL) {
323                                 fclose(fileConnToChild);
324                         }
326                         // Wait for child process to finish.
327                         waitpid(childPID, &status, 0);
328                 }
329         }
330         AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
332         return err;
336 ///////////////////////////////////
337 // Execution thread starts here
338 ///////////////////////////////////
339 static void *Execute (void *arg)
341     EventRef event;
342     
343     taskDone = false;
344     
345     OSErr err = ExecuteScript(scriptPath, &pid);
346     if (err == (OSErr)11) {
347         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindX11Failed, 0,
348                     kEventAttributeNone, &event);
349         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
350     }
351     else if (err == (OSErr)12) {
352         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindFCCacheFailed, 0,
353                     kEventAttributeNone, &event);
354         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityHigh);
355     }
356     else ExitToShell();
357     return 0;
360 ///////////////////////////////////
361 // Open additional documents thread starts here
362 ///////////////////////////////////
363 static void *OpenDoc (void *arg)
365     ExecuteScript(openDocPath, NULL);
366     return 0;
369 ///////////////////////////////////////
370 // Run a script via the system command
371 ///////////////////////////////////////
372 static OSErr ExecuteScript (char *script, pid_t *pid)
374     pid_t wpid = 0, p = 0;
375     int status, i;
376  
377     if (! pid) pid = &p;
379     // Generate the array of argument strings before we do any executing
380     arguments[0] = script;
381     for (i = 0; i < numArgs; i++) arguments[i + 1] = fileArgs[i];
382     arguments[i + 1] = NULL;
384     *pid = fork(); //open fork
385     
386     if (*pid == (pid_t)-1) exit(13); //error
387     else if (*pid == 0) { //child process started
388         execve(arguments[0], arguments, environ);
389         exit(13); //if we reach this point, there's an error
390     }
392     wpid = waitpid(*pid, &status, 0); //wait while child process finishes
393     
394     if (wpid == (pid_t)-1) return wpid;
395     return (OSErr)WEXITSTATUS(status);
398 #pragma mark -
400 ///////////////////////////////////////
401 // This function loads all the neccesary settings
402 // from config files in the Resources folder
403 ///////////////////////////////////////
404 static void GetParameters (void)
406     char *str;
407     if (! (str = (char *)GetScript())) //get path to script to be executed
408         RedFatalAlert("\pInitialization Error",
409                       "\pError getting script from application bundle.");
410     strcpy((char *)&scriptPath, str);
411     
412     if (! (str = (char *)GetOpenDoc())) //get path to openDoc
413         RedFatalAlert("\pInitialization Error",
414                       "\pError getting openDoc from application bundle.");
415     strcpy((char *)&openDocPath, str);
418 ///////////////////////////////////////
419 // Get path to the script in Resources folder
420 ///////////////////////////////////////
421 static char* GetScript (void)
423     CFStringRef fileName;
424     CFBundleRef appBundle;
425     CFURLRef scriptFileURL;
426     FSRef fileRef;
427     FSSpec fileSpec;
428     char *path;
430     //get CF URL for script
431     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
432     if (! (fileName = CFStringCreateWithCString(NULL, kScriptFileName,
433                                                 kCFStringEncodingASCII)))
434         return NULL;
435     if (! (scriptFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
436                                                    NULL))) return NULL;
437     
438     //Get file reference from Core Foundation URL
439     if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL;
440     
441     //dispose of the CF variables
442     CFRelease(scriptFileURL);
443     CFRelease(fileName);
444     
445     //convert FSRef to FSSpec
446     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
447                          NULL)) return NULL;
448         
449     //create path string
450     if (! (path = malloc(kMaxPathLength))) return NULL;
451     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
452     if (! DoesFileExist(path)) return NULL;
453     
454     return path;
457 ///////////////////////////////////////
458 // Gets the path to openDoc in Resources folder
459 ///////////////////////////////////////
460 static char* GetOpenDoc (void)
462     CFStringRef fileName;
463     CFBundleRef appBundle;
464     CFURLRef openDocFileURL;
465     FSRef fileRef;
466     FSSpec fileSpec;
467     char *path;
468     
469     //get CF URL for openDoc
470     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
471     if (! (fileName = CFStringCreateWithCString(NULL, kOpenDocFileName,
472                                                 kCFStringEncodingASCII)))
473         return NULL;
474     if (! (openDocFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
475                                                     NULL))) return NULL;
476     
477     //Get file reference from Core Foundation URL
478     if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL;
479     
480     //dispose of the CF variables
481     CFRelease(openDocFileURL);
482     CFRelease(fileName);
483         
484     //convert FSRef to FSSpec
485     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
486                          NULL)) return NULL;
488     //create path string
489     if (! (path = malloc(kMaxPathLength))) return NULL;
490     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
491     if (! DoesFileExist(path)) return NULL;
492     
493     return path;
496 #pragma mark -
498 /////////////////////////////////////
499 // Load menu bar from nib
500 /////////////////////////////////////
501 OSErr LoadMenuBar (char *appName)
503     OSErr err;
504     IBNibRef nibRef;
505     
506     if (err = CreateNibReference(CFSTR("MenuBar"), &nibRef)) return err;
507     if (err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"))) return err;
508     DisposeNibReference(nibRef);
510     return noErr;
513 #pragma mark -
515 ///////////////////////////////////////
516 // Generate path string from FSSpec record
517 ///////////////////////////////////////
518 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize)
520     OSErr err = noErr;
521     FSRef fileRef;
523     //create file reference from file spec
524     if (err = FSpMakeFSRef(&file, &fileRef)) return err;
526     // and then convert the FSRef to a path
527     return FSRefMakePath(&fileRef, path, maxPathSize);
530 ////////////////////////////////////////
531 // Standard red error alert, then exit application
532 ////////////////////////////////////////
533 static void RedFatalAlert (Str255 errorString, Str255 expStr)
535     StandardAlert(kAlertStopAlert, errorString,  expStr, NULL, NULL);
536     ExitToShell();
539 ///////////////////////////////////////
540 // Determines whether file exists at path or not
541 ///////////////////////////////////////
542 static short DoesFileExist (char *path)
544     if (access(path, F_OK) == -1) return false;
545     return true;        
548 #pragma mark -
550 ///////////////////////////////////////
551 // Apple Event handler for Quit i.e. from
552 // the dock or Application menu item
553 ///////////////////////////////////////
554 static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
555                               AppleEvent *reply, long refCon)
557     #pragma unused (reply, refCon, theAppleEvent)
559     while (numArgs > 0) free(fileArgs[numArgs--]);
560     
561     if (! taskDone && pid) { //kill the script process brutally
562         kill(pid, 9);
563         printf("Platypus App: PID %d killed brutally\n", pid);
564     }
565     
566     pthread_cancel(tid);
567     if (odtid) pthread_cancel(odtid);
568     
569     ExitToShell();
570     
571     return noErr;
574 /////////////////////////////////////
575 // Handler for docs dragged on app icon
576 /////////////////////////////////////
577 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
578                                  AppleEvent *reply, long refCon)
580     #pragma unused (reply, refCon)
581         
582     OSErr err = noErr;
583     AEDescList fileSpecList;
584     AEKeyword keyword;
585     DescType type;
586         
587     short i;
588     long count, actualSize;
589         
590     FSSpec fileSpec;
591     char path[kMaxPathLength];
592     
593     while (numArgs > 0) free(fileArgs[numArgs--]);
594         
595     //Read the AppleEvent
596     err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
597                          &fileSpecList);
598                 
599     err = AECountItems(&fileSpecList, &count); //Count number of files
600                 
601     for (i = 1; i <= count; i++) { //iteratively process each file
602         //get fsspec from apple event
603         if (! (err = AEGetNthPtr(&fileSpecList, i, typeFSS, &keyword, &type,
604                                  (Ptr)&fileSpec, sizeof(FSSpec), &actualSize)))
605         {
606             //get path from file spec
607             if ((err = FSMakePath(fileSpec, (unsigned char *)&path,
608                                   kMaxPathLength))) return err;
609                             
610             if (numArgs == kMaxArgumentsToScript) break;
612             if (! (fileArgs[numArgs] = malloc(kMaxPathLength))) return true;
614             strcpy(fileArgs[numArgs++], (char *)&path);
615         }
616         else return err;
617     }
618         
619     if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL);
620     else pthread_create(&tid, NULL, Execute, NULL);
621         
622     return err;
625 ///////////////////////////////
626 // Handler for clicking on app icon
627 ///////////////////////////////
628 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
629                                  AppleEvent *reply, long refCon)
631     #pragma unused (reply, refCon, theAppleEvent)
632         
633     // the app has been opened without any items dragged on to it
634     pthread_create(&tid, NULL, Execute, NULL);
636     return noErr;
640 static void OpenURL(Str255 url)
642         // Use Internet Config to hand the URL to the appropriate application, as
643         // set by the user in the Internet Preferences pane.
644         ICInstance icInstance;
645         // Applications creator code:
646         OSType signature = 'Inks';
647         OSStatus error = ICStart( &icInstance, signature );
648         if ( error == noErr )
649         {
650                 ConstStr255Param hint = 0x0;
651                 const char* data = url;
652                 long length = strlen(url);
653                 long start =  0;
654                 long end = length;
655                 // Don't bother testing return value (error); launched application will
656                 // report problems.
657                 ICLaunchURL( icInstance, hint, data, length, &start, &end );
658                 ICStop( icInstance );
659         }
663 //////////////////////////////////
664 // Handler for when X11 fails to start
665 //////////////////////////////////
666 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall, 
667                                  EventRef theEvent, void *userData)
669     #pragma unused(theHanderCall, theEvent, userData)
671     pthread_join(tid, NULL);
672     if (odtid) pthread_join(odtid, NULL);
673  
674         SInt16 itemHit;
675         const char *getX11 = "\pGet X11 for Panther";
677         AlertStdAlertParamRec params;
678         params.movable = true;
679         params.helpButton = false;
680         params.filterProc = NULL;
681         params.defaultText = (StringPtr) kAlertDefaultOKText;
682         params.cancelText = getX11;
683         params.otherText = NULL;
684         params.defaultButton = kAlertStdAlertOKButton;
685         params.cancelButton = kAlertStdAlertCancelButton;
686         params.position = kWindowDefaultPosition;
688         StandardAlert(kAlertStopAlert, "\pFailed to start X11",
689                         "\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.",
690                         &params, &itemHit);
691     
692         if (itemHit == kAlertStdAlertCancelButton)
693         {
694                 OpenURL("http://www.apple.com/downloads/macosx/apple/x11formacosx.html");
695         }
697     ExitToShell();
700     return noErr;