979d850572b6983c6a0cb085d05b5c8f715e5d0d
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[])
123 {
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.");
154 GetParameters(); //load data from files containing exec settings
156 RunApplicationEventLoop(); //Run the event loop
157 return 0;
158 }
160 #pragma mark -
163 //////////////////////////////////
164 // Handler for when X11 fails to start
165 //////////////////////////////////
166 static OSStatus FCCacheFailedHandler(EventHandlerCallRef theHandlerCall,
167 EventRef theEvent, void *userData)
168 {
170 pthread_join(tid, NULL);
171 if (odtid) pthread_join(odtid, NULL);
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 ¶ms, &itemHit);
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.", ¶ms, &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.", ¶ms, &itemHit);
211 system("test -d \"$HOME/.inkscape\" || mkdir \"$HOME/.inkscape\"; touch \"$HOME/.inkscape/.fccache\"");
212 }
214 ExitToShell();
216 return noErr;
217 }
220 static size_t safeRead(int d, void *buf, size_t nbytes)
221 {
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;
250 }
253 /////////////////////////////////////
254 // Code to run fc-cache on first run
255 /////////////////////////////////////
256 static OSStatus FixFCCache (void)
257 {
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);
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;
327 }
329 ///////////////////////////////////
330 // Execution thread starts here
331 ///////////////////////////////////
332 static void *Execute (void *arg)
333 {
334 EventRef event;
336 taskDone = false;
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;
351 }
353 ///////////////////////////////////
354 // Open additional documents thread starts here
355 ///////////////////////////////////
356 static void *OpenDoc (void *arg)
357 {
358 ExecuteScript(openDocPath, NULL);
359 return 0;
360 }
362 ///////////////////////////////////////
363 // Run a script via the system command
364 ///////////////////////////////////////
365 static OSErr ExecuteScript (char *script, pid_t *pid)
366 {
367 pid_t wpid = 0, p = 0;
368 int status, i;
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
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
387 if (wpid == (pid_t)-1) return wpid;
388 return (OSErr)WEXITSTATUS(status);
389 }
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)
398 {
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);
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);
409 }
411 ///////////////////////////////////////
412 // Get path to the script in Resources folder
413 ///////////////////////////////////////
414 static char* GetScript (void)
415 {
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;
431 //Get file reference from Core Foundation URL
432 if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL;
434 //dispose of the CF variables
435 CFRelease(scriptFileURL);
436 CFRelease(fileName);
438 //convert FSRef to FSSpec
439 if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
440 NULL)) return NULL;
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;
447 return path;
448 }
450 ///////////////////////////////////////
451 // Gets the path to openDoc in Resources folder
452 ///////////////////////////////////////
453 static char* GetOpenDoc (void)
454 {
455 CFStringRef fileName;
456 CFBundleRef appBundle;
457 CFURLRef openDocFileURL;
458 FSRef fileRef;
459 FSSpec fileSpec;
460 char *path;
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;
470 //Get file reference from Core Foundation URL
471 if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL;
473 //dispose of the CF variables
474 CFRelease(openDocFileURL);
475 CFRelease(fileName);
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;
486 return path;
487 }
489 #pragma mark -
491 /////////////////////////////////////
492 // Load menu bar from nib
493 /////////////////////////////////////
494 OSErr LoadMenuBar (char *appName)
495 {
496 OSErr err;
497 IBNibRef nibRef;
499 if (err = CreateNibReference(CFSTR("MenuBar"), &nibRef)) return err;
500 if (err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"))) return err;
501 DisposeNibReference(nibRef);
503 return noErr;
504 }
506 #pragma mark -
508 ///////////////////////////////////////
509 // Generate path string from FSSpec record
510 ///////////////////////////////////////
511 static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize)
512 {
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);
521 }
523 ////////////////////////////////////////
524 // Standard red error alert, then exit application
525 ////////////////////////////////////////
526 static void RedFatalAlert (Str255 errorString, Str255 expStr)
527 {
528 StandardAlert(kAlertStopAlert, errorString, expStr, NULL, NULL);
529 ExitToShell();
530 }
532 ///////////////////////////////////////
533 // Determines whether file exists at path or not
534 ///////////////////////////////////////
535 static short DoesFileExist (char *path)
536 {
537 if (access(path, F_OK) == -1) return false;
538 return true;
539 }
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)
549 {
550 #pragma unused (reply, refCon, theAppleEvent)
552 while (numArgs > 0) free(fileArgs[numArgs--]);
554 if (! taskDone && pid) { //kill the script process brutally
555 kill(pid, 9);
556 printf("Platypus App: PID %d killed brutally\n", pid);
557 }
559 pthread_cancel(tid);
560 if (odtid) pthread_cancel(odtid);
562 ExitToShell();
564 return noErr;
565 }
567 /////////////////////////////////////
568 // Handler for docs dragged on app icon
569 /////////////////////////////////////
570 static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
571 AppleEvent *reply, long refCon)
572 {
573 #pragma unused (reply, refCon)
575 OSErr err = noErr;
576 AEDescList fileSpecList;
577 AEKeyword keyword;
578 DescType type;
580 short i;
581 long count, actualSize;
583 FSSpec fileSpec;
584 char path[kMaxPathLength];
586 while (numArgs > 0) free(fileArgs[numArgs--]);
588 //Read the AppleEvent
589 err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
590 &fileSpecList);
592 err = AECountItems(&fileSpecList, &count); //Count number of files
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;
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 }
612 if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL);
613 else pthread_create(&tid, NULL, Execute, NULL);
615 return err;
616 }
618 ///////////////////////////////
619 // Handler for clicking on app icon
620 ///////////////////////////////
621 static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
622 AppleEvent *reply, long refCon)
623 {
624 #pragma unused (reply, refCon, theAppleEvent)
626 // the app has been opened without any items dragged on to it
627 pthread_create(&tid, NULL, Execute, NULL);
629 return noErr;
630 }
633 static void OpenURL(Str255 url)
634 {
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 }
653 }
656 //////////////////////////////////
657 // Handler for when X11 fails to start
658 //////////////////////////////////
659 static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
660 EventRef theEvent, void *userData)
661 {
662 #pragma unused(theHanderCall, theEvent, userData)
664 pthread_join(tid, NULL);
665 if (odtid) pthread_join(odtid, NULL);
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 ¶ms, &itemHit);
685 if (itemHit == kAlertStdAlertCancelButton)
686 {
687 OpenURL("http://www.apple.com/downloads/macosx/apple/x11formacosx.html");
688 }
690 ExitToShell();
693 return noErr;
694 }