| 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 | } |
|---|