Code

* packaging/macosx/ScriptExec/main.c, packaging/macosx/Resources/script:
[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);
221     return err;
225 static size_t safeRead(int d, void *buf, size_t nbytes)
227         ssize_t bytesToRead = nbytes;
228         ssize_t bytesRead = 0;
229         char *offset = (char *) buf;
231         while ((bytesToRead > 0))
232         {
233                 bytesRead = read(d, offset, bytesToRead);
234                 if (bytesRead > 0)
235                 {
236                         offset += bytesRead;
237                         bytesToRead -= bytesRead;
238                 }
239                 else if (bytesRead == 0)
240                 {       
241                         // Reached EOF.
242                         break;
243                 }
244                 else if (bytesRead == -1)
245                 {
246                         if ((errno == EINTR) || (errno == EAGAIN))
247                         {
248                                 // Try again.
249                                 continue;
250                         }
251                         return 0;
252                 }
253         }
254         return bytesRead;
258 /////////////////////////////////////
259 // Code to run fc-cache on first run
260 /////////////////////////////////////
261 static OSStatus FixFCCache (void)
263         FILE *fileConnToChild = NULL;
264         int fdConnToChild = 0;
265         pid_t childPID = WAIT_ANY;
266         size_t bytesChildPID;
267         size_t bytesRead;
268         int status;
270         char commandStr[] = "/usr/X11R6/bin/fc-cache";
271         char *commandArgs[] = { "-f", NULL };
273         // Run fc-cache
274         AuthorizationItem authItems[] = 
275         {
276         {
277                 kAuthorizationRightExecute,
278                 strlen(commandStr),
279                 commandStr,
280                 0
281         }
282         };
283         AuthorizationItemSet authItemSet =
284         {
285                 1,
286                 authItems
287         };
288         AuthorizationRef authRef = NULL;
289         OSStatus err = AuthorizationCreate (NULL, &authItemSet,
290                         kAuthorizationFlagInteractionAllowed | 
291                         kAuthorizationFlagExtendRights, &authRef);
293         if (err == errAuthorizationSuccess)
294         {
295                 err = AuthorizationExecuteWithPrivileges(authRef, commandStr, 
296                                 kAuthorizationFlagDefaults, commandArgs,
297                                 &fileConnToChild);
299                 if (err == errAuthorizationSuccess)
300                 {
301                         // Unfortunately, AuthorizationExecuteWithPrivileges
302                         // does not return the process ID associated with the
303                         // process it runs.  The best solution we have it to
304                         // try and get the process ID from the file descriptor.
305                         // This is based on example code from Apple's
306                         // MoreAuthSample.
308                         fdConnToChild = fileno(fileConnToChild);
309                         
310                         // Try an get the process ID of the fc-cache command
311                         bytesChildPID = sizeof(childPID);
312                         bytesRead = safeRead(fdConnToChild, &childPID,
313                                         bytesChildPID);
314                         if (bytesRead != bytesChildPID)
315                         {
316                                 // If we can't get it the best alternative
317                                 // is to wait for any child to finish.
318                                 childPID = WAIT_ANY;
319                         }
321                         if (fileConnToChild != NULL) {
322                                 fclose(fileConnToChild);
323                         }
325                         // Wait for child process to finish.
326                         waitpid(childPID, &status, 0);
327                 }
328         }
329         AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
331         return err;
335 ///////////////////////////////////
336 // Execution thread starts here
337 ///////////////////////////////////
338 static void *Execute (void *arg)
340     EventRef event;
341     
342     taskDone = false;
343     
344     OSErr err = ExecuteScript(scriptPath, &pid);
345     if (err == (OSErr)11) {
346         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindX11Failed, 0,
347                     kEventAttributeNone, &event);
348         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
349     }
350     else if (err == (OSErr)12) {
351         CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindFCCacheFailed, 0,
352                     kEventAttributeNone, &event);
353         PostEventToQueue(GetMainEventQueue(), event, kEventPriorityHigh);
354     }
355     else ExitToShell();
356     return 0;
359 ///////////////////////////////////
360 // Open additional documents thread starts here
361 ///////////////////////////////////
362 static void *OpenDoc (void *arg)
364     ExecuteScript(openDocPath, NULL);
365     return 0;
368 ///////////////////////////////////////
369 // Run a script via the system command
370 ///////////////////////////////////////
371 static OSErr ExecuteScript (char *script, pid_t *pid)
373     pid_t wpid = 0, p = 0;
374     int status, i;
375  
376     if (! pid) pid = &p;
378     // Generate the array of argument strings before we do any executing
379     arguments[0] = script;
380     for (i = 0; i < numArgs; i++) arguments[i + 1] = fileArgs[i];
381     arguments[i + 1] = NULL;
383     *pid = fork(); //open fork
384     
385     if (*pid == (pid_t)-1) exit(13); //error
386     else if (*pid == 0) { //child process started
387         execve(arguments[0], arguments, environ);
388         exit(13); //if we reach this point, there's an error
389     }
391     wpid = waitpid(*pid, &status, 0); //wait while child process finishes
392     
393     if (wpid == (pid_t)-1) return wpid;
394     return (OSErr)WEXITSTATUS(status);
397 #pragma mark -
399 ///////////////////////////////////////
400 // This function loads all the neccesary settings
401 // from config files in the Resources folder
402 ///////////////////////////////////////
403 static void GetParameters (void)
405     char *str;
406     if (! (str = (char *)GetScript())) //get path to script to be executed
407         RedFatalAlert("\pInitialization Error",
408                       "\pError getting script from application bundle.");
409     strcpy((char *)&scriptPath, str);
410     
411     if (! (str = (char *)GetOpenDoc())) //get path to openDoc
412         RedFatalAlert("\pInitialization Error",
413                       "\pError getting openDoc from application bundle.");
414     strcpy((char *)&openDocPath, str);
417 ///////////////////////////////////////
418 // Get path to the script in Resources folder
419 ///////////////////////////////////////
420 static char* GetScript (void)
422     CFStringRef fileName;
423     CFBundleRef appBundle;
424     CFURLRef scriptFileURL;
425     FSRef fileRef;
426     FSSpec fileSpec;
427     char *path;
429     //get CF URL for script
430     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
431     if (! (fileName = CFStringCreateWithCString(NULL, kScriptFileName,
432                                                 kCFStringEncodingASCII)))
433         return NULL;
434     if (! (scriptFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
435                                                    NULL))) return NULL;
436     
437     //Get file reference from Core Foundation URL
438     if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL;
439     
440     //dispose of the CF variables
441     CFRelease(scriptFileURL);
442     CFRelease(fileName);
443     
444     //convert FSRef to FSSpec
445     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
446                          NULL)) return NULL;
447         
448     //create path string
449     if (! (path = malloc(kMaxPathLength))) return NULL;
450     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
451     if (! DoesFileExist(path)) return NULL;
452     
453     return path;
456 ///////////////////////////////////////
457 // Gets the path to openDoc in Resources folder
458 ///////////////////////////////////////
459 static char* GetOpenDoc (void)
461     CFStringRef fileName;
462     CFBundleRef appBundle;
463     CFURLRef openDocFileURL;
464     FSRef fileRef;
465     FSSpec fileSpec;
466     char *path;
467     
468     //get CF URL for openDoc
469     if (! (appBundle = CFBundleGetMainBundle())) return NULL;
470     if (! (fileName = CFStringCreateWithCString(NULL, kOpenDocFileName,
471                                                 kCFStringEncodingASCII)))
472         return NULL;
473     if (! (openDocFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
474                                                     NULL))) return NULL;
475     
476     //Get file reference from Core Foundation URL
477     if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL;
478     
479     //dispose of the CF variables
480     CFRelease(openDocFileURL);
481     CFRelease(fileName);
482         
483     //convert FSRef to FSSpec
484     if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
485                          NULL)) return NULL;
487     //create path string
488     if (! (path = malloc(kMaxPathLength))) return NULL;
489     if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
490     if (! DoesFileExist(path)) return NULL;
491     
492     return path;
495 #pragma mark -
497 /////////////////////////////////////
498 // Load menu bar from nib
499 /////////////////////////////////////
500 OSErr LoadMenuBar (char *appName)
502     OSErr err;
503     IBNibRef nibRef;
504     
505     if (err = CreateNibReference(CFSTR("MenuBar"), &nibRef)) return err;
506     if (err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"))) return err;
507     DisposeNibReference(nibRef);
509     return noErr;
512 #pragma mark -
514 ///////////////////////////////////////
515 // Generate path string from FSSpec record
516 ///////////////////////////////////////
517 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize)
519     OSErr err = noErr;
520     FSRef fileRef;
522     //create file reference from file spec
523     if (err = FSpMakeFSRef(&file, &fileRef)) return err;
525     // and then convert the FSRef to a path
526     return FSRefMakePath(&fileRef, path, maxPathSize);
529 ////////////////////////////////////////
530 // Standard red error alert, then exit application
531 ////////////////////////////////////////
532 static void RedFatalAlert (Str255 errorString, Str255 expStr)
534     StandardAlert(kAlertStopAlert, errorString,  expStr, NULL, NULL);
535     ExitToShell();
538 ///////////////////////////////////////
539 // Determines whether file exists at path or not
540 ///////////////////////////////////////
541 static short DoesFileExist (char *path)
543     if (access(path, F_OK) == -1) return false;
544     return true;        
547 #pragma mark -
549 ///////////////////////////////////////
550 // Apple Event handler for Quit i.e. from
551 // the dock or Application menu item
552 ///////////////////////////////////////
553 static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
554                               AppleEvent *reply, long refCon)
556     #pragma unused (reply, refCon, theAppleEvent)
558     while (numArgs > 0) free(fileArgs[numArgs--]);
559     
560     if (! taskDone && pid) { //kill the script process brutally
561         kill(pid, 9);
562         printf("Platypus App: PID %d killed brutally\n", pid);
563     }
564     
565     pthread_cancel(tid);
566     if (odtid) pthread_cancel(odtid);
567     
568     ExitToShell();
569     
570     return noErr;
573 /////////////////////////////////////
574 // Handler for docs dragged on app icon
575 /////////////////////////////////////
576 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
577                                  AppleEvent *reply, long refCon)
579     #pragma unused (reply, refCon)
580         
581     OSErr err = noErr;
582     AEDescList fileSpecList;
583     AEKeyword keyword;
584     DescType type;
585         
586     short i;
587     long count, actualSize;
588         
589     FSSpec fileSpec;
590     char path[kMaxPathLength];
591     
592     while (numArgs > 0) free(fileArgs[numArgs--]);
593         
594     //Read the AppleEvent
595     err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
596                          &fileSpecList);
597                 
598     err = AECountItems(&fileSpecList, &count); //Count number of files
599                 
600     for (i = 1; i <= count; i++) { //iteratively process each file
601         //get fsspec from apple event
602         if (! (err = AEGetNthPtr(&fileSpecList, i, typeFSS, &keyword, &type,
603                                  (Ptr)&fileSpec, sizeof(FSSpec), &actualSize)))
604         {
605             //get path from file spec
606             if ((err = FSMakePath(fileSpec, (unsigned char *)&path,
607                                   kMaxPathLength))) return err;
608                             
609             if (numArgs == kMaxArgumentsToScript) break;
611             if (! (fileArgs[numArgs] = malloc(kMaxPathLength))) return true;
613             strcpy(fileArgs[numArgs++], (char *)&path);
614         }
615         else return err;
616     }
617         
618     if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL);
619     else pthread_create(&tid, NULL, Execute, NULL);
620         
621     return err;
624 ///////////////////////////////
625 // Handler for clicking on app icon
626 ///////////////////////////////
627 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
628                                  AppleEvent *reply, long refCon)
630     #pragma unused (reply, refCon, theAppleEvent)
631         
632     // the app has been opened without any items dragged on to it
633     pthread_create(&tid, NULL, Execute, NULL);
635     return noErr;
639 static void OpenURL(Str255 url)
641         // Use Internet Config to hand the URL to the appropriate application, as
642         // set by the user in the Internet Preferences pane.
643         ICInstance icInstance;
644         // Applications creator code:
645         OSType signature = 'Inks';
646         OSStatus error = ICStart( &icInstance, signature );
647         if ( error == noErr )
648         {
649                 ConstStr255Param hint = 0x0;
650                 const char* data = url;
651                 long length = strlen(url);
652                 long start =  0;
653                 long end = length;
654                 // Don't bother testing return value (error); launched application will
655                 // report problems.
656                 ICLaunchURL( icInstance, hint, data, length, &start, &end );
657                 ICStop( icInstance );
658         }
662 //////////////////////////////////
663 // Handler for when X11 fails to start
664 //////////////////////////////////
665 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall, 
666                                  EventRef theEvent, void *userData)
668     #pragma unused(theHanderCall, theEvent, userData)
670     pthread_join(tid, NULL);
671     if (odtid) pthread_join(odtid, NULL);
672  
673         SInt16 itemHit;
674         const char *getX11 = "\pGet X11 for Panther";
676         AlertStdAlertParamRec params;
677         params.movable = true;
678         params.helpButton = false;
679         params.filterProc = NULL;
680         params.defaultText = (StringPtr) kAlertDefaultOKText;
681         params.cancelText = getX11;
682         params.otherText = NULL;
683         params.defaultButton = kAlertStdAlertOKButton;
684         params.cancelButton = kAlertStdAlertCancelButton;
685         params.position = kWindowDefaultPosition;
687         StandardAlert(kAlertStopAlert, "\pFailed to start X11",
688                         "\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.",
689                         &params, &itemHit);
690     
691         if (itemHit == kAlertStdAlertCancelButton)
692         {
693                 OpenURL("http://www.apple.com/downloads/macosx/apple/x11formacosx.html");
694         }
696     ExitToShell();
699     return noErr;