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> |
---|
5 | |
---|
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. |
---|
10 | |
---|
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. |
---|
15 | |
---|
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. |
---|
19 | |
---|
20 | main.c - main program file |
---|
21 | |
---|
22 | */ |
---|
23 | |
---|
24 | /////////////////////////////////////// |
---|
25 | // Includes |
---|
26 | /////////////////////////////////////// |
---|
27 | #pragma mark Includes |
---|
28 | |
---|
29 | // Apple stuff |
---|
30 | #include <Carbon/Carbon.h> |
---|
31 | #include <CoreFoundation/CoreFoundation.h> |
---|
32 | |
---|
33 | // Unix stuff |
---|
34 | #include <string.h> |
---|
35 | #include <unistd.h> |
---|
36 | #include <sys/wait.h> |
---|
37 | #include <pthread.h> |
---|
38 | |
---|
39 | /////////////////////////////////////// |
---|
40 | // Definitions |
---|
41 | /////////////////////////////////////// |
---|
42 | #pragma mark Definitions |
---|
43 | |
---|
44 | // name length limits |
---|
45 | #define kMaxPathLength 1024 |
---|
46 | |
---|
47 | // names of files bundled with app |
---|
48 | #define kScriptFileName "script" |
---|
49 | #define kOpenDocFileName "openDoc" |
---|
50 | |
---|
51 | // custom carbon events |
---|
52 | #define kEventClassRedFatalAlert 911 |
---|
53 | #define kEventKindX11Failed 911 |
---|
54 | |
---|
55 | //maximum arguments the script accepts |
---|
56 | #define kMaxArgumentsToScript 252 |
---|
57 | |
---|
58 | /////////////////////////////////////// |
---|
59 | // Prototypes |
---|
60 | /////////////////////////////////////// |
---|
61 | #pragma mark Prototypes |
---|
62 | |
---|
63 | static void *Execute(void *arg); |
---|
64 | static void *OpenDoc(void *arg); |
---|
65 | static OSErr ExecuteScript(char *script, pid_t *pid); |
---|
66 | |
---|
67 | static void GetParameters(void); |
---|
68 | static char* GetScript(void); |
---|
69 | static char* GetOpenDoc(void); |
---|
70 | |
---|
71 | OSErr LoadMenuBar(char *appName); |
---|
72 | |
---|
73 | static void RedFatalAlert(Str255 errorString, Str255 expStr); |
---|
74 | static short DoesFileExist(char *path); |
---|
75 | |
---|
76 | static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent, |
---|
77 | AppleEvent *reply, long refCon); |
---|
78 | static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent, |
---|
79 | AppleEvent *reply, long refCon); |
---|
80 | static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent, |
---|
81 | AppleEvent *reply, long refCon); |
---|
82 | static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall, |
---|
83 | EventRef theEvent, void *userData); |
---|
84 | |
---|
85 | /////////////////////////////////////// |
---|
86 | // Globals |
---|
87 | /////////////////////////////////////// |
---|
88 | #pragma mark Globals |
---|
89 | |
---|
90 | // process id of forked process |
---|
91 | pid_t pid = 0; |
---|
92 | |
---|
93 | // thread id of threads that start scripts |
---|
94 | pthread_t odtid = 0, tid = 0; |
---|
95 | |
---|
96 | // indicator of whether the script has completed executing |
---|
97 | short taskDone = true; |
---|
98 | |
---|
99 | // execution parameters |
---|
100 | char scriptPath[kMaxPathLength]; |
---|
101 | char openDocPath[kMaxPathLength]; |
---|
102 | |
---|
103 | //arguments to the script |
---|
104 | char *arguments[kMaxArgumentsToScript+3]; |
---|
105 | char *fileArgs[kMaxArgumentsToScript]; |
---|
106 | short numArgs = 0; |
---|
107 | |
---|
108 | extern char **environ; |
---|
109 | |
---|
110 | #pragma mark - |
---|
111 | |
---|
112 | /////////////////////////////////////// |
---|
113 | // Program entrance point |
---|
114 | /////////////////////////////////////// |
---|
115 | int main(int argc, char* argv[]) |
---|
116 | { |
---|
117 | OSErr err = noErr; |
---|
118 | EventTypeSpec events = { kEventClassRedFatalAlert, kEventKindX11Failed }; |
---|
119 | |
---|
120 | InitCursor(); |
---|
121 | |
---|
122 | //install Apple Event handlers |
---|
123 | err += AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, |
---|
124 | NewAEEventHandlerUPP(AppQuitAEHandler), |
---|
125 | 0, false); |
---|
126 | err += AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, |
---|
127 | NewAEEventHandlerUPP(AppOpenDocAEHandler), |
---|
128 | 0, false); |
---|
129 | err += AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, |
---|
130 | NewAEEventHandlerUPP(AppOpenAppAEHandler), |
---|
131 | 0, false); |
---|
132 | err += InstallEventHandler(GetApplicationEventTarget(), |
---|
133 | NewEventHandlerUPP(X11FailedHandler), 1, |
---|
134 | &events, NULL, NULL); |
---|
135 | |
---|
136 | if (err) RedFatalAlert("\pInitialization Error", |
---|
137 | "\pError initing Apple Event handlers."); |
---|
138 | |
---|
139 | //create the menu bar |
---|
140 | if (err = LoadMenuBar(NULL)) RedFatalAlert("\pInitialization Error", |
---|
141 | "\pError loading MenuBar.nib."); |
---|
142 | |
---|
143 | GetParameters(); //load data from files containing exec settings |
---|
144 | |
---|
145 | RunApplicationEventLoop(); //Run the event loop |
---|
146 | return 0; |
---|
147 | } |
---|
148 | |
---|
149 | #pragma mark - |
---|
150 | |
---|
151 | /////////////////////////////////// |
---|
152 | // Execution thread starts here |
---|
153 | /////////////////////////////////// |
---|
154 | static void *Execute (void *arg) |
---|
155 | { |
---|
156 | EventRef event; |
---|
157 | |
---|
158 | taskDone = false; |
---|
159 | if (ExecuteScript(scriptPath, &pid) == (OSErr)11) { |
---|
160 | CreateEvent(NULL, kEventClassRedFatalAlert, kEventKindX11Failed, 0, |
---|
161 | kEventAttributeNone, &event); |
---|
162 | PostEventToQueue(GetMainEventQueue(), event, kEventPriorityStandard); |
---|
163 | } |
---|
164 | else ExitToShell(); |
---|
165 | return 0; |
---|
166 | } |
---|
167 | |
---|
168 | /////////////////////////////////// |
---|
169 | // Open additional documents thread starts here |
---|
170 | /////////////////////////////////// |
---|
171 | static void *OpenDoc (void *arg) |
---|
172 | { |
---|
173 | ExecuteScript(openDocPath, NULL); |
---|
174 | return 0; |
---|
175 | } |
---|
176 | |
---|
177 | /////////////////////////////////////// |
---|
178 | // Run a script via the system command |
---|
179 | /////////////////////////////////////// |
---|
180 | static OSErr ExecuteScript (char *script, pid_t *pid) |
---|
181 | { |
---|
182 | pid_t wpid = 0, p = 0; |
---|
183 | int status, i; |
---|
184 | |
---|
185 | if (! pid) pid = &p; |
---|
186 | |
---|
187 | // Generate the array of argument strings before we do any executing |
---|
188 | arguments[0] = script; |
---|
189 | for (i = 0; i < numArgs; i++) arguments[i + 1] = fileArgs[i]; |
---|
190 | arguments[i + 1] = NULL; |
---|
191 | |
---|
192 | *pid = fork(); //open fork |
---|
193 | |
---|
194 | if (*pid == (pid_t)-1) exit(13); //error |
---|
195 | else if (*pid == 0) { //child process started |
---|
196 | execve(arguments[0], arguments, environ); |
---|
197 | exit(13); //if we reach this point, there's an error |
---|
198 | } |
---|
199 | |
---|
200 | wpid = waitpid(*pid, &status, 0); //wait while child process finishes |
---|
201 | |
---|
202 | if (wpid == (pid_t)-1) return wpid; |
---|
203 | return (OSErr)WEXITSTATUS(status); |
---|
204 | } |
---|
205 | |
---|
206 | #pragma mark - |
---|
207 | |
---|
208 | /////////////////////////////////////// |
---|
209 | // This function loads all the neccesary settings |
---|
210 | // from config files in the Resources folder |
---|
211 | /////////////////////////////////////// |
---|
212 | static void GetParameters (void) |
---|
213 | { |
---|
214 | char *str; |
---|
215 | if (! (str = (char *)GetScript())) //get path to script to be executed |
---|
216 | RedFatalAlert("\pInitialization Error", |
---|
217 | "\pError getting script from application bundle."); |
---|
218 | strcpy((char *)&scriptPath, str); |
---|
219 | |
---|
220 | if (! (str = (char *)GetOpenDoc())) //get path to openDoc |
---|
221 | RedFatalAlert("\pInitialization Error", |
---|
222 | "\pError getting openDoc from application bundle."); |
---|
223 | strcpy((char *)&openDocPath, str); |
---|
224 | } |
---|
225 | |
---|
226 | /////////////////////////////////////// |
---|
227 | // Get path to the script in Resources folder |
---|
228 | /////////////////////////////////////// |
---|
229 | static char* GetScript (void) |
---|
230 | { |
---|
231 | CFStringRef fileName; |
---|
232 | CFBundleRef appBundle; |
---|
233 | CFURLRef scriptFileURL; |
---|
234 | FSRef fileRef; |
---|
235 | char *path; |
---|
236 | |
---|
237 | //get CF URL for script |
---|
238 | if (! (appBundle = CFBundleGetMainBundle())) return NULL; |
---|
239 | if (! (fileName = CFStringCreateWithCString(NULL, kScriptFileName, |
---|
240 | kCFStringEncodingASCII))) |
---|
241 | return NULL; |
---|
242 | if (! (scriptFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL, |
---|
243 | NULL))) return NULL; |
---|
244 | |
---|
245 | //Get file reference from Core Foundation URL |
---|
246 | if (! CFURLGetFSRef(scriptFileURL, &fileRef)) return NULL; |
---|
247 | |
---|
248 | //dispose of the CF variables |
---|
249 | CFRelease(scriptFileURL); |
---|
250 | CFRelease(fileName); |
---|
251 | |
---|
252 | //create path string |
---|
253 | if (! (path = malloc(kMaxPathLength))) return NULL; |
---|
254 | if (FSRefMakePath(&fileRef, path, kMaxPathLength)) return NULL; |
---|
255 | if (! DoesFileExist(path)) return NULL; |
---|
256 | |
---|
257 | return path; |
---|
258 | } |
---|
259 | |
---|
260 | /////////////////////////////////////// |
---|
261 | // Gets the path to openDoc in Resources folder |
---|
262 | /////////////////////////////////////// |
---|
263 | static char* GetOpenDoc (void) |
---|
264 | { |
---|
265 | CFStringRef fileName; |
---|
266 | CFBundleRef appBundle; |
---|
267 | CFURLRef openDocFileURL; |
---|
268 | FSRef fileRef; |
---|
269 | char *path; |
---|
270 | |
---|
271 | //get CF URL for openDoc |
---|
272 | if (! (appBundle = CFBundleGetMainBundle())) return NULL; |
---|
273 | if (! (fileName = CFStringCreateWithCString(NULL, kOpenDocFileName, |
---|
274 | kCFStringEncodingASCII))) |
---|
275 | return NULL; |
---|
276 | if (! (openDocFileURL = CFBundleCopyResourceURL(appBundle, fileName, NULL, |
---|
277 | NULL))) return NULL; |
---|
278 | |
---|
279 | //Get file reference from Core Foundation URL |
---|
280 | if (! CFURLGetFSRef( openDocFileURL, &fileRef )) return NULL; |
---|
281 | |
---|
282 | //dispose of the CF variables |
---|
283 | CFRelease(openDocFileURL); |
---|
284 | CFRelease(fileName); |
---|
285 | |
---|
286 | //create path string |
---|
287 | if (! (path = malloc(kMaxPathLength))) return NULL; |
---|
288 | if (FSRefMakePath(&fileRef, path, kMaxPathLength)) return NULL; |
---|
289 | if (! DoesFileExist(path)) return NULL; |
---|
290 | |
---|
291 | return path; |
---|
292 | } |
---|
293 | |
---|
294 | #pragma mark - |
---|
295 | |
---|
296 | ///////////////////////////////////// |
---|
297 | // Load menu bar from nib |
---|
298 | ///////////////////////////////////// |
---|
299 | OSErr LoadMenuBar (char *appName) |
---|
300 | { |
---|
301 | OSErr err; |
---|
302 | IBNibRef nibRef; |
---|
303 | |
---|
304 | if (err = CreateNibReference(CFSTR("MenuBar"), &nibRef)) return err; |
---|
305 | if (err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"))) return err; |
---|
306 | DisposeNibReference(nibRef); |
---|
307 | |
---|
308 | return noErr; |
---|
309 | } |
---|
310 | |
---|
311 | #pragma mark - |
---|
312 | |
---|
313 | //////////////////////////////////////// |
---|
314 | // Standard red error alert, then exit application |
---|
315 | //////////////////////////////////////// |
---|
316 | static void RedFatalAlert (Str255 errorString, Str255 expStr) |
---|
317 | { |
---|
318 | // StandardAlert(kAlertStopAlert, errorString, expStr, NULL, NULL); |
---|
319 | ExitToShell(); |
---|
320 | } |
---|
321 | |
---|
322 | /////////////////////////////////////// |
---|
323 | // Determines whether file exists at path or not |
---|
324 | /////////////////////////////////////// |
---|
325 | static short DoesFileExist (char *path) |
---|
326 | { |
---|
327 | if (access(path, F_OK) == -1) return false; |
---|
328 | return true; |
---|
329 | } |
---|
330 | |
---|
331 | #pragma mark - |
---|
332 | |
---|
333 | /////////////////////////////////////// |
---|
334 | // Apple Event handler for Quit i.e. from |
---|
335 | // the dock or Application menu item |
---|
336 | /////////////////////////////////////// |
---|
337 | static OSErr AppQuitAEHandler(const AppleEvent *theAppleEvent, |
---|
338 | AppleEvent *reply, long refCon) |
---|
339 | { |
---|
340 | #pragma unused (reply, refCon, theAppleEvent) |
---|
341 | |
---|
342 | while (numArgs > 0) free(fileArgs[numArgs--]); |
---|
343 | |
---|
344 | if (! taskDone && pid) { //kill the script process brutally |
---|
345 | kill(pid, 9); |
---|
346 | printf("Platypus App: PID %d killed brutally\n", pid); |
---|
347 | } |
---|
348 | |
---|
349 | pthread_cancel(tid); |
---|
350 | if (odtid) pthread_cancel(odtid); |
---|
351 | |
---|
352 | ExitToShell(); |
---|
353 | |
---|
354 | return noErr; |
---|
355 | } |
---|
356 | |
---|
357 | ///////////////////////////////////// |
---|
358 | // Handler for docs dragged on app icon |
---|
359 | ///////////////////////////////////// |
---|
360 | static OSErr AppOpenDocAEHandler(const AppleEvent *theAppleEvent, |
---|
361 | AppleEvent *reply, long refCon) |
---|
362 | { |
---|
363 | #pragma unused (reply, refCon) |
---|
364 | |
---|
365 | OSErr err = noErr; |
---|
366 | AEDescList fileSpecList; |
---|
367 | AEKeyword keyword; |
---|
368 | DescType type; |
---|
369 | |
---|
370 | short i; |
---|
371 | long count, actualSize; |
---|
372 | |
---|
373 | FSRef fileRef; |
---|
374 | char path[kMaxPathLength]; |
---|
375 | |
---|
376 | while (numArgs > 0) free(fileArgs[numArgs--]); |
---|
377 | |
---|
378 | //Read the AppleEvent |
---|
379 | err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList, |
---|
380 | &fileSpecList); |
---|
381 | |
---|
382 | err = AECountItems(&fileSpecList, &count); //Count number of files |
---|
383 | |
---|
384 | for (i = 1; i <= count; i++) { //iteratively process each file |
---|
385 | //get fsref from apple event |
---|
386 | if (! (err = AEGetNthPtr(&fileSpecList, i, typeFSRef, &keyword, &type, |
---|
387 | (Ptr)&fileRef, sizeof(FSRef), &actualSize))) |
---|
388 | { |
---|
389 | //get path from file spec |
---|
390 | if ((err = FSRefMakePath(&fileRef, (unsigned char *)&path, |
---|
391 | kMaxPathLength))) return err; |
---|
392 | |
---|
393 | if (numArgs == kMaxArgumentsToScript) break; |
---|
394 | |
---|
395 | if (! (fileArgs[numArgs] = malloc(kMaxPathLength))) return true; |
---|
396 | |
---|
397 | strcpy(fileArgs[numArgs++], (char *)&path); |
---|
398 | } |
---|
399 | else return err; |
---|
400 | } |
---|
401 | |
---|
402 | if (! taskDone) pthread_create(&odtid, NULL, OpenDoc, NULL); |
---|
403 | else pthread_create(&tid, NULL, Execute, NULL); |
---|
404 | |
---|
405 | return err; |
---|
406 | } |
---|
407 | |
---|
408 | /////////////////////////////// |
---|
409 | // Handler for clicking on app icon |
---|
410 | /////////////////////////////// |
---|
411 | static OSErr AppOpenAppAEHandler(const AppleEvent *theAppleEvent, |
---|
412 | AppleEvent *reply, long refCon) |
---|
413 | { |
---|
414 | #pragma unused (reply, refCon, theAppleEvent) |
---|
415 | |
---|
416 | // the app has been opened without any items dragged on to it |
---|
417 | pthread_create(&tid, NULL, Execute, NULL); |
---|
418 | } |
---|
419 | |
---|
420 | ////////////////////////////////// |
---|
421 | // Handler for when X11 fails to start |
---|
422 | ////////////////////////////////// |
---|
423 | static OSStatus X11FailedHandler(EventHandlerCallRef theHandlerCall, |
---|
424 | EventRef theEvent, void *userData) |
---|
425 | { |
---|
426 | #pragma unused(theHanderCall, theEvent, userData) |
---|
427 | |
---|
428 | pthread_join(tid, NULL); |
---|
429 | if (odtid) pthread_join(odtid, NULL); |
---|
430 | |
---|
431 | RedFatalAlert("\pFailed to start X11", |
---|
432 | "\pGimp.app requires Apple's X11."); |
---|
433 | |
---|
434 | return noErr; |
---|
435 | } |
---|