source: trunk/launch/launch/main.c @ 3

Last change on this file since 3 was 3, checked in by Nicholas Riley, 20 years ago

Initial import.

File size: 26.1 KB
Line 
1/*
2 launch - a smarter 'open' replacement
3 Nicholas Riley <launchsw@sabi.net>
4
5 Copyright (c) 2002, Nicholas Riley
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9
10 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
12 * Neither the name of this software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 
16*/
17
18/* To do/think about:
19
20- Do we need to assume -b if -h?  Hiding the foreground app just makes
21it flash (only if Cocoa?)
22
23- Does -X work at all?  What does it return if it fails?
24
25- Create blank document ˆ la my existing BBEdit alias?  Use
26template/stationery?
27
28- Allow piping (like BBEdit, again?)
29
30- Launching as root: use authentication framework - doesn't work.
31
32- launch apps by IC protocol handler (esp. editor)
33
34Thanks to:
35
36- Nat Irons, for encouragement and suggestions
37
38- Brian Hill, for the great Security.framework tutorial and sample code
39
40*/
41
42#define DEBUG 1
43#define BROKEN_AUTHORIZATION 1
44#define BROKEN_LAUNCHNEWINSTANCE 1
45#define kComponentSignatureString "launch"
46
47#include <unistd.h>
48#include <Carbon/Carbon.h>
49#include <CoreServices/CoreServices.h>
50#include <CoreFoundation/CoreFoundation.h>
51#include <ApplicationServices/ApplicationServices.h>
52
53#ifndef BROKEN_AUTHORIZATION
54#include <Security/Authorization.h>
55#include <Security/AuthorizationTags.h>
56#endif
57
58const char *APP_NAME;
59
60#define VERSION "1.0a9"
61
62#define STRBUF_LEN 1024
63#define ACTION_DEFAULT ACTION_OPEN
64
65struct {
66    OSType creator;
67    CFStringRef bundleID, name;
68    enum { ACTION_FIND, ACTION_FIND_ITEMS,
69           ACTION_OPEN, ACTION_OPEN_ITEMS, 
70           ACTION_INFO_ITEMS, ACTION_LAUNCH_URLS } action;
71} OPTS = 
72{
73    kLSUnknownCreator, NULL, NULL,
74    ACTION_DEFAULT
75};
76
77#define DEFAULT_LAUNCH_FLAGS (kLSLaunchNoParams | kLSLaunchStartClassic | kLSLaunchAsync)
78
79LSLaunchURLSpec LSPEC = {NULL, NULL, NULL, DEFAULT_LAUNCH_FLAGS, NULL};
80
81typedef struct {
82    OSStatus status;
83    const char *desc;
84} errRec, errList[];
85
86static errList ERRS = {
87    // Launch Services errors
88    { kLSUnknownErr, "unknown Launch Services error" },
89    { kLSApplicationNotFoundErr, "application not found" },
90    { kLSLaunchInProgressErr, "application is being opened; please try again after the application is open" },
91    { kLSNotRegisteredErr, "application not registered in Launch Services database" },
92#ifndef BROKEN_AUTHORIZATION
93    // Security framework errors
94    { errAuthorizationDenied, "authorization denied" },
95    { errAuthorizationCanceled, "authentication was cancelled" },
96#endif
97    // Internet Config errors
98    { icPrefNotFoundErr, "no helper application is defined for the URL's scheme" },
99    { icNoURLErr, "not a URL" },
100    { icInternalErr, "internal Internet Config error" },
101    // Misc. errors
102    { procNotFound, "unable to connect to system service.\nAre you logged in?" },
103    { 1001, "SystemConfiguration nonspecific failure.\nAre you logged in?" },
104    { fnfErr, "file not found" },
105    { 0, NULL }
106};
107
108void usage() {
109    fprintf(stderr, "usage: %s [-npswbmhCX] [-c creator] [-i bundleID] [-u URL] [-a name] [document ...]\n"
110                    "   or: %s [-npflswbmhCX] item ...\n", APP_NAME, APP_NAME);
111    fprintf(stderr,
112        "  -n            print matching paths/URLs instead of opening them\n"
113        "  -p            ask application(s) to print document(s)\n"
114        "  -f            display information about item(s)\n"
115        "  -l            launch URLs (e.g. treat http:// URLs as Web sites, not WebDAV)\n"
116#ifndef BROKEN_AUTHORIZATION
117        "  -s            launch target(s) as superuser (authenticating if needed)\n"
118#endif
119        "  -w            wait for application to finish opening before exiting\n"
120        "  -b            launch application in the background\n"
121#ifndef BROKEN_LAUNCHNEWINSTANCE
122        "  -m            launch application again, even if already running\n"
123#endif
124        "  -h            hide application once it's finished opening\n"
125        "  -C            force CFM/PEF Carbon application to launch in Classic\n"
126        "  -X            don't start Classic for this app if Classic isn't running\n"
127        "  -c creator    match application by four-character creator code ('ToyS')\n"
128        "  -i bundle ID  match application by bundle identifier (com.apple.scripteditor)\n"
129        "  -u URL        open application at file:// URL (NOT RECOMMENDED for scripts)\n"
130        "  -a name       match application by name (NOT RECOMMENDED, very fragile)\n"
131        "'document' may be a file, folder, or disk - whatever the application can open.\n"
132        "'item' may be a file, folder, disk, or URL.\n\n");
133    fprintf(stderr, "launch "VERSION" (c) 2001-02 Nicholas Riley <http://web.sabi.net/nriley/software/>.\n"
134                    "Please send bugs, suggestions, etc. to <launchsw@sabi.net>.\n");
135
136    exit(1);
137}
138
139char *osstatusstr(OSStatus err) {
140    errRec *rec;
141    const char *errDesc = "unknown error";
142    const char *failedStr = "(unable to retrieve error message)";
143    static char *str = NULL;
144    size_t len;
145    if (str != NULL && str != failedStr) free(str);
146    for (rec = &(ERRS[0]) ; rec->status != 0 ; rec++)
147        if (rec->status == err) {
148            errDesc = rec->desc;
149            break;
150        }
151    len = strlen(errDesc) + 10 * sizeof(char);
152    str = (char *)malloc(len);
153    if (str != NULL)
154        snprintf(str, len, "%s (%d)", errDesc, (long)err);
155    else
156        str = failedStr;
157    return str;
158}
159
160void osstatusexit(OSStatus err, const char *fmt, ...) {
161    va_list ap;
162    const char *errDesc = osstatusstr(err);
163    va_start(ap, fmt);
164    fprintf(stderr, "%s: ", APP_NAME);
165    vfprintf(stderr, fmt, ap);
166    fprintf(stderr, ": %s\n", errDesc);
167    exit(1);
168}
169
170void errexit(const char *fmt, ...) {
171    va_list ap;
172    va_start(ap, fmt);
173    fprintf(stderr, "%s: ", APP_NAME);
174    vfprintf(stderr, fmt, ap);
175    fprintf(stderr, "\n");
176    exit(1);
177}
178
179#ifndef BROKEN_AUTHORIZATION
180
181Boolean authenticated(AuthorizationItem item, AuthorizationRef *pAuthRef) {
182    AuthorizationRights rights;
183    AuthorizationRights *authorizedRights;
184    AuthorizationFlags flags;
185    OSStatus err;
186
187    // Create an AuthorizationRef yet with the kAuthorizationFlagDefaults
188    // flags to get the user's current authorization rights.
189    rights.count = 0;
190    rights.items = NULL;
191   
192    flags = kAuthorizationFlagDefaults;
193
194    err = AuthorizationCreate(&rights,
195        kAuthorizationEmptyEnvironment, flags,
196        pAuthRef);
197       
198    rights.count = 1;
199    rights.items = &item;
200   
201    flags = kAuthorizationFlagExtendRights;
202   
203    // don't ask for a password, just return failure if no rights
204    err = AuthorizationCopyRights(*pAuthRef, &rights,
205        kAuthorizationEmptyEnvironment, flags, &authorizedRights);
206
207    switch (err) {
208    case errAuthorizationSuccess:
209        // we don't need these items, and they need to be disposed of
210        AuthorizationFreeItemSet(authorizedRights);
211        return true;
212    case errAuthorizationInteractionNotAllowed:
213        return false;
214    default:
215        osstatusexit(err, "unable to determine authentication status");
216    }
217    return false; // to satisfy compiler
218}
219
220void authenticate(AuthorizationItem item, AuthorizationRef authorizationRef) {
221    AuthorizationRights rights = {1, &item};
222    AuthorizationRights *authorizedRights;
223    AuthorizationFlags flags;
224    OSStatus err;
225
226    flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
227
228    // Here, since we've specified kAuthorizationFlagExtendRights and
229    // have also specified kAuthorizationFlagInteractionAllowed, if the
230    // user isn't currently authorized to execute tools as root
231    // (kAuthorizationRightExecute), they will be asked for their password.
232    // The err return value will indicate authorization success or failure.
233    err = AuthorizationCopyRights(authorizationRef,&rights,
234                        kAuthorizationEmptyEnvironment,
235                        flags,&authorizedRights);
236   
237    if (errAuthorizationSuccess == err)
238        AuthorizationFreeItemSet(authorizedRights);
239    else
240        osstatusexit(err, "unable to authenticate");
241}
242#endif
243
244void getargs(int argc, const char *argv[]) {
245    extern char *optarg;
246    extern int optind;
247    int ch;
248    Boolean appSpecified = false;
249
250    if (argc == 1) usage();
251   
252    while ( (ch = getopt(argc, argv, "npflswbmhCXc:i:u:a:")) != -1) {
253        switch (ch) {
254        case 'n':
255            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
256            OPTS.action = ACTION_FIND;
257            break;
258        case 'p':
259            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
260            OPTS.action = ACTION_OPEN;
261            LSPEC.launchFlags |= kLSLaunchAndPrint;
262            break;
263        case 'f':
264            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
265            OPTS.action = ACTION_INFO_ITEMS;
266            break;
267        case 'l':
268            if (OPTS.action != ACTION_DEFAULT) errexit("choose only one of -n, -p, -f, -l options");
269            OPTS.action = ACTION_LAUNCH_URLS;
270            break;
271        case 's':
272#ifdef BROKEN_AUTHORIZATION
273            errexit("-s option no longer functional after 10.1 Security Update, sorry");
274#else
275        {
276            AuthorizationRef authRef;
277            AuthorizationItem item = { kAuthorizationRightExecute, strlen(argv[0]), argv[0], 0 };
278            OSStatus err;
279           
280            if (authenticated(item, &authRef)) {
281                continue;
282            }
283            authenticate(item, authRef);
284            err = AuthorizationExecuteWithPrivileges(authRef, argv[0], 0, &argv[1], NULL);
285            if (err != noErr) osstatusexit(err, "unable to launch '%s' with superuser privileges", argv[0]);
286            exit(0); // XXX exit status propagate?
287        }
288#endif
289        case 'w': LSPEC.launchFlags ^= kLSLaunchAsync; break;      // synchronous
290        case 'b': LSPEC.launchFlags |= kLSLaunchDontSwitch; break; // open in background
291#ifdef BROKEN_LAUNCHNEWINSTANCE
292        case 'm': errexit("-m option not functional (LaunchServices bug?), sorry");
293#else
294        case 'm': LSPEC.launchFlags |= kLSLaunchNewInstance; break;// open multiple
295#endif
296        case 'h': LSPEC.launchFlags |= kLSLaunchAndHide; break;    // hide once launched
297        case 'C': LSPEC.launchFlags |= kLSLaunchInClassic; break;  // force Classic
298        case 'X': LSPEC.launchFlags ^= kLSLaunchStartClassic; break;// don't start Classic for app
299        case 'c':
300            if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long");
301            OPTS.creator = *(OSTypePtr)optarg;
302            appSpecified = true;
303            break;
304        case 'i':
305            OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding());
306            appSpecified = true;
307            break;
308        case 'a':
309            OPTS.name = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding());
310            appSpecified = true;
311            break;
312        case 'u':
313            LSPEC.appURL = CFURLCreateWithString(NULL,
314                CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding()), NULL);
315            if (LSPEC.appURL == NULL) {
316                errexit("invalid URL (argument of -u)");
317            } else {
318                CFURLRef absURL = CFURLCopyAbsoluteURL(LSPEC.appURL);
319                CFRelease(LSPEC.appURL);
320                LSPEC.appURL = NULL;
321                if (absURL != NULL) {
322                    CFStringRef scheme = CFURLCopyScheme(absURL);
323                    LSPEC.appURL = absURL;
324                    if (scheme == NULL || !CFEqual(scheme, CFSTR("file")))
325                        errexit("invalid file:// URL (argument of -u)");
326                }
327            }
328            appSpecified = true;
329            break;
330        default: usage();
331        }
332    }
333   
334    argc -= optind;
335    argv += optind;
336   
337    if ( (OPTS.action == ACTION_FIND || OPTS.action == ACTION_LAUNCH_URLS ||
338          OPTS.action == ACTION_INFO_ITEMS) && LSPEC.launchFlags != DEFAULT_LAUNCH_FLAGS)
339        errexit("options -s, -b, -m, -h, -C, -X apply to application launch (not -n, -f or -l)");
340   
341    if (OPTS.creator == kLSUnknownCreator && OPTS.bundleID == NULL && OPTS.name == NULL) {
342        if (argc == 0 && LSPEC.appURL == NULL)
343            errexit("must specify an application by -u, or one or more of -c, -i, -a");
344        if (!appSpecified) {
345            if (OPTS.action == ACTION_FIND)
346                OPTS.action = ACTION_FIND_ITEMS;
347            if (OPTS.action == ACTION_OPEN)
348                OPTS.action = ACTION_OPEN_ITEMS;
349        }
350    } else {
351        if (LSPEC.appURL != NULL)
352            errexit("application URL (argument of -u) incompatible with matching by -c, -i, -a");
353    }
354
355    if (OPTS.action == ACTION_LAUNCH_URLS && appSpecified)
356        errexit("sorry, launching URLs with a given application is not yet supported"); // XXX
357
358    if (OPTS.action == ACTION_INFO_ITEMS && appSpecified)
359        errexit("can't get information (-f) on item(s) using an application (-u, -c, -i, -a)");
360
361    if (argc == 0 && OPTS.action == ACTION_OPEN && LSPEC.launchFlags & kLSLaunchAndPrint)
362        errexit("print option (-p) must be accompanied by document(s) to print");
363   
364    if (argc != 0) {
365        int i;
366        OSStatus err;
367        CFStringRef pathstr;
368        CFURLRef itemURL;
369        LSItemInfoRecord docInfo;
370
371        if (OPTS.action == ACTION_FIND)
372            errexit("application with documents only supported for open or print, not find");
373
374        // handle document/item/URL arguments
375        LSPEC.itemURLs = CFArrayCreateMutable(NULL, argc, NULL);
376        for (i = 0 ; i < argc ; i++) {
377            pathstr = CFStringCreateWithCString(NULL, argv[i], CFStringGetSystemEncoding());
378            itemURL = NULL;
379            if (OPTS.action == ACTION_FIND_ITEMS || OPTS.action == ACTION_OPEN_ITEMS ||
380                OPTS.action == ACTION_LAUNCH_URLS || OPTS.action == ACTION_INFO_ITEMS) { // check for URLs
381                itemURL = CFURLCreateWithString(NULL, pathstr, NULL);
382                if (itemURL != NULL) {
383                    CFURLRef absURL = CFURLCopyAbsoluteURL(itemURL);
384                    CFRelease(itemURL);
385                    itemURL = NULL;
386                    if (absURL != NULL) {
387                        CFStringRef scheme = CFURLCopyScheme(absURL);
388                        itemURL = absURL;
389                        if (scheme == NULL) {
390                            CFRelease(itemURL);
391                            itemURL = NULL;
392                        }
393                    }
394                }
395            }
396            if (itemURL == NULL) {
397                itemURL = CFURLCreateWithFileSystemPath(NULL, pathstr, kCFURLPOSIXPathStyle, false);
398                err = LSCopyItemInfoForURL(itemURL, kLSRequestExtensionFlagsOnly, &docInfo);
399                if (err != noErr) osstatusexit(err, "unable to locate '%s'", argv[i]);
400            }
401            CFArrayAppendValue((CFMutableArrayRef)LSPEC.itemURLs, itemURL);
402            CFRelease(pathstr);
403        }
404    }
405}
406
407// 'context' is to match prototype for CFArrayApplierFunction, it's unused
408void printPathFromURL(CFURLRef url, void *context) {
409    CFStringRef scheme, pathOrURL;
410    static char strBuffer[STRBUF_LEN];
411   
412    check(url != NULL && context == NULL);
413
414    scheme = CFURLCopyScheme(url);
415   
416    if (CFEqual(scheme, CFSTR("file")))
417        pathOrURL = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
418    else
419        pathOrURL = CFURLGetString(url);
420
421    strBuffer[0] = '\0';
422    CFStringGetCString(pathOrURL, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX buffer size issues?
423    printf("%s\n", strBuffer);
424    CFRelease(scheme);
425    CFRelease(pathOrURL);
426}
427
428void printDateTime(const char *label, UTCDateTime *utcTime, const char *postLabel, Boolean printIfEmpty) {
429    static Str255 dateStr, timeStr;
430    LocalDateTime localTime;
431    LongDateTime longTime;
432    OSStatus err;
433
434    err = ConvertUTCToLocalDateTime(utcTime, &localTime);
435    if (err == kUTCUnderflowErr) {
436        if (printIfEmpty) printf("\t%s: (not set)\n", label);
437        return;
438    }
439    if (err != noErr) osstatusexit(err, "unable to convert UTC %s date to local", label);
440
441    longTime = localTime.highSeconds;
442    longTime <<= 32;
443    longTime |= localTime.lowSeconds;
444
445    // strings include trailing newlines; strip them.
446    LongDateString(&longTime, shortDate, dateStr, nil); dateStr[dateStr[0] + 1] = '\0';
447    LongTimeString(&longTime, true, timeStr, nil); timeStr[timeStr[0] + 1] = '\0';
448    printf("\t%s: %s %s%s\n", label, dateStr + 1, timeStr + 1, postLabel);
449}
450
451#define DFORMAT(SIZE) ((float)(SIZE) / 1024.)
452
453void printSizes(const char *label, UInt64 logicalSize, UInt64 physicalSize, Boolean printIfZero) {
454    UInt32 bigSize = physicalSize >> 32, littleSize = physicalSize;
455    if (!printIfZero && bigSize == 0 && littleSize == 0) return;
456    printf("\t%s: ", label);
457    if (bigSize == 0) {
458        if (littleSize == 0) {
459            printf("zero bytes on disk (zero bytes used)\n"); return;
460        } else if (littleSize < 1024) printf("%lu bytes", littleSize);
461        else {
462            UInt32 adjSize = littleSize >> 10;
463            if (adjSize < 1024) printf("%.1f KB", DFORMAT(littleSize));
464            else {
465                adjSize >>= 10; littleSize >>= 10;
466                if (adjSize < 1024) printf("%.2f MB", DFORMAT(littleSize));
467                else {
468                    adjSize >>= 10; littleSize >>= 10;
469                    printf("%.2f GB", DFORMAT(littleSize));
470                }
471            }
472        }
473    } else {
474        if (bigSize < 256) printf("%lu GB", bigSize);
475        else {
476            bigSize >>= 2;
477            printf("%lu TB", bigSize);
478        }
479    }
480    printf(" on disk (%llu bytes used)\n", logicalSize);
481       
482}
483
484void printMoreInfoFromURL(CFURLRef url) {
485    FSRef fsr;
486    OSStatus err;
487    FSCatalogInfo fscInfo;
488
489    if (!CFURLGetFSRef(url, &fsr)) return;
490    err = FSGetCatalogInfo(&fsr, kFSCatInfoNodeFlags | kFSCatInfoAllDates | kFSCatInfoDataSizes | kFSCatInfoRsrcSizes | kFSCatInfoValence, &fscInfo, NULL, NULL, NULL);
491    if (err != noErr) osstatusexit(err, "unable to get catalog information for file");
492
493    if (fscInfo.nodeFlags & kFSNodeIsDirectoryMask) {
494        printf("\tcontents: %lu item%s\n", fscInfo.valence, fscInfo.valence != 1 ? "s" : "");
495    } else {
496        printSizes("data fork size", fscInfo.dataLogicalSize, fscInfo.dataPhysicalSize, true);
497        printSizes("rsrc fork size", fscInfo.rsrcLogicalSize, fscInfo.rsrcPhysicalSize, false);
498    }
499
500    if (fscInfo.nodeFlags & (kFSNodeLockedMask | kFSNodeForkOpenMask)) {
501        printf("\tstatus:");
502        if (fscInfo.nodeFlags & kFSNodeLockedMask) {
503            if (fscInfo.nodeFlags & kFSNodeForkOpenMask) printf(" in use,");
504            printf(" locked");
505        } else {
506            printf(" in use");
507        }
508        printf("\n");
509    }
510   
511    printDateTime("created", &fscInfo.createDate, "", true);
512    printDateTime("modified", &fscInfo.contentModDate, "", true);
513    printDateTime("accessed", &fscInfo.accessDate, " [only updated by Mac OS X]", false);
514    printDateTime("backed up", &fscInfo.backupDate, "", false);
515}
516
517// 'context' is to match prototype for CFArrayApplierFunction, it's unused
518void printInfoFromURL(CFURLRef url, void *context) {
519    CFStringRef scheme, pathOrURL, kind;
520    Boolean isRemote;
521    static char strBuffer[STRBUF_LEN], tmpBuffer[STRBUF_LEN];
522   
523    check(url != NULL && context == NULL);
524
525    scheme = CFURLCopyScheme(url);
526   
527    isRemote = !CFEqual(scheme, CFSTR("file"));
528    if (isRemote)
529        pathOrURL = CFURLGetString(url);
530    else
531        pathOrURL = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
532
533    strBuffer[0] = '\0';
534    CFStringGetCString(pathOrURL, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX buffer size issues?
535    if (isRemote)
536        printf("<%s>: URL\n", strBuffer);
537    else {
538        static LSItemInfoRecord info;
539        OSStatus err = LSCopyItemInfoForURL(url, kLSRequestAllInfo, &info);
540        if (err != noErr) osstatusexit(err, "unable to get information about '%s'", strBuffer);
541       
542        printf("%s: ", strBuffer);
543       
544        // modifiers
545        if (info.flags & kLSItemInfoIsInvisible) printf("invisible ");
546        if (info.flags & kLSItemInfoAppIsScriptable) printf("scriptable ");
547        if (info.flags & kLSItemInfoIsNativeApp) printf("Mac OS X ");
548        if (info.flags & kLSItemInfoIsClassicApp) printf("Classic ");
549       
550        // kind
551        if (info.flags & kLSItemInfoIsVolume) printf("volume");
552        else if (info.flags & kLSItemInfoIsApplication) printf("application ");
553        else if (info.flags & kLSItemInfoIsPackage) printf("non-application ");
554        else if (info.flags & kLSItemInfoIsContainer) printf("folder");
555        else if (info.flags & kLSItemInfoIsAliasFile) printf("alias");
556        else if (info.flags & kLSItemInfoIsSymlink) printf("symbolic link");
557        else if (info.flags & kLSItemInfoIsPlainFile) printf("document");
558        else printf("unknown file system entity");
559
560        if (info.flags & kLSItemInfoIsPackage) printf("package ");
561
562        if (info.flags & kLSItemInfoAppPrefersNative) printf("[Carbon, prefers native OS X]");
563        else if (info.flags & kLSItemInfoAppPrefersClassic) printf("[Carbon, prefers Classic]");
564
565        printf("\n");
566        if (!(info.flags & kLSItemInfoIsContainer) || info.flags & kLSItemInfoIsPackage) {
567            tmpBuffer[4] = '\0';
568            strncpy(tmpBuffer, (char *)&info.filetype, 4); printf("\ttype: '%s'", tmpBuffer);
569            strncpy(tmpBuffer, (char *)&info.creator, 4); printf("\tcreator: '%s'\n", tmpBuffer);
570        }
571        if (info.flags & kLSItemInfoIsPackage ||
572                info.flags & kLSItemInfoIsApplication && info.flags & kLSItemInfoIsNativeApp) {
573                // a package, or possibly a native app with a 'plst' resource
574            CFBundleRef bundle = CFBundleCreate(NULL, url);
575            CFStringRef bundleID;
576            if (bundle == NULL) { // OS X bug causes this to fail when it shouldn't, so just note it, don't die
577                if (info.flags & kLSItemInfoIsApplication) printf("\t[can't access CFBundle for application]\n");
578            } else {
579                bundleID = CFBundleGetIdentifier(bundle);
580                if (bundleID != NULL) {
581                    CFStringGetCString(bundleID, tmpBuffer, STRBUF_LEN, CFStringGetSystemEncoding());
582                    printf("\tbundle ID: %s\n", tmpBuffer);
583                }
584                CFRelease(bundle);
585            }
586        }
587       
588        // kind string
589        err = LSCopyKindStringForURL(url, &kind);
590        if (err != noErr) osstatusexit(err, "unable to get kind of '%s'", strBuffer);
591        CFStringGetCString(kind, tmpBuffer, STRBUF_LEN, CFStringGetSystemEncoding());
592        printf("\tkind: %s\n", tmpBuffer);
593        printMoreInfoFromURL(url);
594    }
595    CFRelease(scheme);
596    CFRelease(pathOrURL);
597}
598
599
600void launchURL(CFURLRef url, ICInstance icInst) {
601    CFStringRef urlStr = CFURLGetString(url);
602    static char strBuffer[STRBUF_LEN];
603    long strStart, strEnd;
604    OSStatus err;
605
606    strBuffer[0] = '\0';
607    CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX buffer size issues?
608    strStart = 0;
609    strEnd = strlen(strBuffer);
610    err = ICLaunchURL(icInst, "\p", strBuffer, strEnd, &strStart, &strEnd);
611    if (err != noErr) {
612        fprintf(stderr, "%s: unable to launch URL <%s>: %s\n", APP_NAME, strBuffer, osstatusstr(err));
613    }
614   
615    CFRelease(urlStr);
616}
617
618int main (int argc, const char *argv[]) {
619    OSStatus err;
620   
621    APP_NAME = argv[0];
622    getargs(argc, argv);
623
624    if (OPTS.action == ACTION_FIND || OPTS.action == ACTION_OPEN) {
625        if (LSPEC.appURL != NULL) goto findOK; // already have an application URL
626        err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, NULL, &LSPEC.appURL);
627       
628        if (err != noErr) {
629            if (OPTS.name != NULL && !CFStringHasSuffix(OPTS.name, CFSTR(".app"))) {
630                OPTS.name = CFStringCreateMutableCopy(NULL, CFStringGetLength(OPTS.name) + 4, OPTS.name);
631                CFStringAppend((CFMutableStringRef)OPTS.name, CFSTR(".app"));
632                err = LSFindApplicationForInfo(OPTS.creator, OPTS.bundleID, OPTS.name, NULL, &LSPEC.appURL);
633                if (err == noErr) goto findOK;
634            }
635            osstatusexit(err, "can't locate application", argv[1]);
636        findOK: ;
637        }
638    }
639   
640    switch (OPTS.action) {
641    case ACTION_FIND:
642        printPathFromURL(LSPEC.appURL, NULL);
643        break;
644    case ACTION_OPEN:
645        err = LSOpenFromURLSpec(&LSPEC, NULL);
646        if (err != noErr) osstatusexit(err, "can't open application", argv[1]);
647        break;
648    case ACTION_FIND_ITEMS:
649        CFArrayApplyFunction(LSPEC.itemURLs, CFRangeMake(0, CFArrayGetCount(LSPEC.itemURLs)),
650                             (CFArrayApplierFunction) printPathFromURL, NULL);
651        break;
652    case ACTION_OPEN_ITEMS:
653        err = LSOpenFromURLSpec(&LSPEC, NULL);
654        if (err != noErr) osstatusexit(err, "can't open items", argv[1]);
655        break;
656    case ACTION_INFO_ITEMS:
657        CFArrayApplyFunction(LSPEC.itemURLs, CFRangeMake(0, CFArrayGetCount(LSPEC.itemURLs)),
658                             (CFArrayApplierFunction) printInfoFromURL, NULL);
659        break;
660    case ACTION_LAUNCH_URLS:
661    {
662        ICInstance icInst;
663        err = ICStart(&icInst, '????');
664        if (err != noErr) osstatusexit(err, "can't initialize Internet Config", argv[1]);
665        CFArrayApplyFunction(LSPEC.itemURLs, CFRangeMake(0, CFArrayGetCount(LSPEC.itemURLs)),
666                             (CFArrayApplierFunction) launchURL, icInst);
667        ICStop(icInst);
668        break;
669    }
670    }
671
672    return 0;
673}
Note: See TracBrowser for help on using the repository browser.