2
Platypus - create MacOS X application bundles that execute scripts
3
This is the executable that goes into Platypus apps
4
Copyright (C) 2003, 2008 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
24
///////////////////////////////////////
26
///////////////////////////////////////
30
#include <Carbon/Carbon.h>
31
#include <CoreFoundation/CoreFoundation.h>
39
///////////////////////////////////////
41
///////////////////////////////////////
42
#pragma mark Definitions
45
#define kMaxPathLength 1024
47
// names of files bundled with app
48
#define kScriptFileName "script"
49
#define kOpenDocFileName "openDoc"
51
// custom carbon events
52
#define kEventClassRedFatalAlert 911
53
#define kEventKindX11Failed 911
55
//maximum arguments the script accepts
56
#define kMaxArgumentsToScript 252
58
///////////////////////////////////////
60
///////////////////////////////////////
61
#pragma mark Prototypes
63
static void *Execute(void *arg);
64
static void *OpenDoc(void *arg);
65
static OSErr ExecuteScript(char *script, pid_t *pid);
67
static void GetParameters(void);
68
static char* GetScript(void);
69
static char* GetOpenDoc(void);
71
OSErr LoadMenuBar(char *appName);
73
static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize);
74
static void RedFatalAlert(Str255 errorString, Str255 expStr);
75
static short DoesFileExist(char *path);
77
static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
78
AppleEvent *reply, long refCon);
79
static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
80
AppleEvent *reply, long refCon);
81
static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
82
AppleEvent *reply, long refCon);
83
static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
84
EventRef theEvent, void *userData);
86
///////////////////////////////////////
88
///////////////////////////////////////
91
// process id of forked process
94
// thread id of threads that start scripts
95
pthread_t odtid = 0, tid = 0;
97
// indicator of whether the script has completed executing
98
short taskDone = true;
100
// execution parameters
101
char scriptPath[kMaxPathLength];
102
char openDocPath[kMaxPathLength];
104
//arguments to the script
105
char *arguments[kMaxArgumentsToScript+3];
106
char *fileArgs[kMaxArgumentsToScript];
109
extern char **environ;
113
///////////////////////////////////////
114
// Program entrance point
115
///////////////////////////////////////
116
int main(int argc, char* argv[])
119
EventTypeSpec events = { kEventClassRedFatalAlert, kEventKindX11Failed };
124
//install Apple Event handlers
125
err += AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
126
NewAEEventHandlerUPP(AppQuitAEHandler),
128
err += AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
129
NewAEEventHandlerUPP(AppOpenDocAEHandler),
131
err += AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
132
NewAEEventHandlerUPP(AppOpenAppAEHandler),
134
err += InstallEventHandler(GetApplicationEventTarget(),
135
NewEventHandlerUPP(X11FailedHandler), 1,
136
&events, NULL, NULL);
138
if (err) RedFatalAlert("\pInitialization Error",
139
"\pError initing Apple Event handlers.");
141
//create the menu bar
142
if ((err = LoadMenuBar(NULL))) RedFatalAlert("\pInitialization Error",
143
"\pError loading MenuBar.nib.");
145
GetParameters(); //load data from files containing exec settings
147
RunApplicationEventLoop(); //Run the event loop
153
///////////////////////////////////
154
// Execution thread starts here
155
///////////////////////////////////
156
static void *Execute (void *arg)
161
if (ExecuteScript(scriptPath, &pid) == (OSErr)11) {
162
CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindX11Failed, 0,
163
kEventAttributeNone, &event);
164
PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard);
170
///////////////////////////////////
171
// Open additional documents thread starts here
172
///////////////////////////////////
173
static void *OpenDoc (void *arg)
175
ExecuteScript(openDocPath, NULL);
179
///////////////////////////////////////
180
// Run a script via the system command
181
///////////////////////////////////////
182
static OSErr ExecuteScript (char *script, pid_t *pid)
184
pid_t wpid = 0, p = 0;
189
// Generate the array of argument strings before we do any executing
190
arguments[0] = script;
191
for (i = 0; i < numArgs; i++) arguments[i + 1] = fileArgs[i];
192
arguments[i + 1] = NULL;
194
*pid = fork(); //open fork
196
if (*pid == (pid_t)-1) exit(13); //error
197
else if (*pid == 0) { //child process started
198
execve(arguments[0], arguments, environ);
199
exit(13); //if we reach this point, there's an error
202
wpid = waitpid(*pid, &status, 0); //wait while child process finishes
204
if (wpid == (pid_t)-1) return wpid;
205
return (OSErr)WEXITSTATUS(status);
210
///////////////////////////////////////
211
// This function loads all the neccesary settings
212
// from config files in the Resources folder
213
///////////////////////////////////////
214
static void GetParameters (void)
217
if (! (str = (char *)GetScript())) //get path to script to be executed
218
RedFatalAlert("\pInitialization Error",
219
"\pError getting script from application bundle.");
220
strcpy((char *)&scriptPath, str);
222
if (! (str = (char *)GetOpenDoc())) //get path to openDoc
223
RedFatalAlert("\pInitialization Error",
224
"\pError getting openDoc from application bundle.");
225
strcpy((char *)&openDocPath, str);
228
///////////////////////////////////////
229
// Get path to the script in Resources folder
230
///////////////////////////////////////
231
static char* GetScript (void)
233
CFStringRef fileName;
234
CFBundleRef appBundle;
235
CFURLRef scriptFileURL;
240
//get CF URL for script
241
if (! (appBundle = CFBundleGetMainBundle())) return NULL;
242
if (! (fileName = CFStringCreateWithCString(NULL, kScriptFileName,
243
kCFStringEncodingASCII)))
245
if (! (scriptFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
248
//Get file reference from Core Foundation URL
249
if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL;
251
//dispose of the CF variables
252
CFRelease(scriptFileURL);
255
//convert FSRef to FSSpec
256
if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
260
if (! (path = malloc(kMaxPathLength))) return NULL;
261
if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
262
if (! DoesFileExist(path)) return NULL;
267
///////////////////////////////////////
268
// Gets the path to openDoc in Resources folder
269
///////////////////////////////////////
270
static char* GetOpenDoc (void)
272
CFStringRef fileName;
273
CFBundleRef appBundle;
274
CFURLRef openDocFileURL;
279
//get CF URL for openDoc
280
if (! (appBundle = CFBundleGetMainBundle())) return NULL;
281
if (! (fileName = CFStringCreateWithCString(NULL, kOpenDocFileName,
282
kCFStringEncodingASCII)))
284
if (! (openDocFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL,
287
//Get file reference from Core Foundation URL
288
if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL;
290
//dispose of the CF variables
291
CFRelease(openDocFileURL);
294
//convert FSRef to FSSpec
295
if (FSGetCatalogInfo(&fileRef, kFSCatInfoNone, NULL, NULL, &fileSpec,
299
if (! (path = malloc(kMaxPathLength))) return NULL;
300
if (FSMakePath(fileSpec, path, kMaxPathLength)) return NULL;
301
if (! DoesFileExist(path)) return NULL;
308
/////////////////////////////////////
309
// Load menu bar from nib
310
/////////////////////////////////////
311
OSErr LoadMenuBar (char *appName)
316
if ((err = CreateNibReference(CFSTR("MenuBar"), &nibRef))) return err;
317
if ((err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar")))) return err;
318
DisposeNibReference(nibRef);
325
///////////////////////////////////////
326
// Generate path string from FSSpec record
327
///////////////////////////////////////
328
static OSStatus FSMakePath(FSSpec file, char *path, long maxPathSize)
333
//create file reference from file spec
334
if ((err = FSpMakeFSRef(&file, &fileRef))) return err;
336
// and then convert the FSRef to a path
337
return FSRefMakePath(&fileRef, (UInt8 *)path, maxPathSize);
340
////////////////////////////////////////
341
// Standard red error alert, then exit application
342
////////////////////////////////////////
343
static void RedFatalAlert (Str255 errorString, Str255 expStr)
345
StandardAlert(kAlertStopAlert, errorString, expStr, NULL, NULL);
349
///////////////////////////////////////
350
// Determines whether file exists at path or not
351
///////////////////////////////////////
352
static short DoesFileExist (char *path)
354
if (access(path, F_OK) == -1) return false;
360
///////////////////////////////////////
361
// Apple Event handler for Quit i.e. from
362
// the dock or Application menu item
363
///////////////////////////////////////
364
static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent,
365
AppleEvent *reply, long refCon)
367
#pragma unused (reply, refCon, theAppleEvent)
369
while (numArgs > 0) free(fileArgs[numArgs--]);
371
if (! taskDone && pid) { //kill the script process brutally
373
printf("Platypus App: PID %d killed brutally\n", pid);
377
if (odtid) pthread_cancel(odtid);
384
/////////////////////////////////////
385
// Handler for docs dragged on app icon
386
/////////////////////////////////////
387
static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent,
388
AppleEvent *reply, long refCon)
390
#pragma unused (reply, refCon)
393
AEDescList fileSpecList;
398
long count, actualSize;
401
char path[kMaxPathLength];
403
while (numArgs > 0) free(fileArgs[numArgs--]);
405
//Read the AppleEvent
406
err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
409
err = AECountItems(&fileSpecList, &count); //Count number of files
411
for (i = 1; i <= count; i++) { //iteratively process each file
412
//get fsspec from apple event
413
if (! (err = AEGetNthPtr(&fileSpecList, i, typeFSS, &keyword, &type,
414
(Ptr)&fileSpec, sizeof(FSSpec), &actualSize)))
416
//get path from file spec
417
if ((err = FSMakePath(fileSpec, path,
418
kMaxPathLength))) return err;
420
if (numArgs == kMaxArgumentsToScript) break;
422
if (! (fileArgs[numArgs] = malloc(kMaxPathLength))) return true;
424
strcpy(fileArgs[numArgs++], (char *)&path);
429
if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL);
430
else pthread_create(&tid, NULL, Execute, NULL);
435
///////////////////////////////
436
// Handler for clicking on app icon
437
///////////////////////////////
438
static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent,
439
AppleEvent *reply, long refCon)
441
#pragma unused (reply, refCon, theAppleEvent)
443
// the app has been opened without any items dragged on to it
444
pthread_create(&tid, NULL, Execute, NULL);
449
//////////////////////////////////
450
// Handler for when X11 fails to start
451
//////////////////////////////////
452
static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall,
453
EventRef theEvent, void *userData)
455
#pragma unused(theHanderCall, theEvent, userData)
457
pthread_join(tid, NULL);
458
if (odtid) pthread_join(odtid, NULL);
460
RedFatalAlert("\pFailed to start X11",
461
"\pGimp.app requires Apple's X11.");