Ignore:
Timestamp:
04/04/05 00:22:09 (19 years ago)
Author:
Nicholas Riley
Message:

VERSION: Updated for 1.0.1.

main.c: Updated copyright statement. Updated for 1.0.1. Added -U,
triggers OPTS.forceURLs. Added kLSMultipleSessionsNotSupportedErr,
nsvErr. Cleaned up string encoding handling; works much better now.
Split code into stringFromURLIsRemote, utf8StringFromCFStringRef, and
utf8StringFromOSType. Display "contents: zero items" instead of "0
items" in printMoreInfoForURL. Remove extraneous "./" at beginning of
displayed paths. Get versions of non-{applications, packages} and
info from nonbundled apps with CFBundleCopyInfoDictionaryForURL.
Replaced some error codes with numbers so we support building on 10.2
again.

launch.1: Updated for 1.0.1 and -U option.

README: Updated for 1.0.1. Fixed a paste-o in the uninstallation
instructions.

package-launch.sh: Use zsh explicitly. Build as deployment. Fix
permissions. Make tarball contents owned by root/wheel.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/launch/launch/main.c

    r148 r166  
    33 Nicholas Riley <launchsw@sabi.net>
    44
    5  Copyright (c) 2001-03, Nicholas Riley
     5 Copyright (c) 2001-05, Nicholas Riley
    66 All rights reserved.
    77
     
    5151const char *APP_NAME;
    5252
    53 #define VERSION "1.0"
     53#define VERSION "1.0.1"
    5454
    5555#define STRBUF_LEN 1024
     
    5959    OSType creator;
    6060    CFStringRef bundleID, name;
     61    Boolean forceURLs;
    6162    enum { ACTION_FIND, ACTION_FIND_ITEMS,
    6263           ACTION_OPEN, ACTION_OPEN_ITEMS,
     
    6465} OPTS =
    6566{
    66     kLSUnknownCreator, NULL, NULL,
     67    kLSUnknownCreator, NULL, NULL, false,
    6768    ACTION_DEFAULT
    6869};
     
    8788    { -10827, "application package contains no executable, or an unusable executable" }, /* kLSNoExecutableErr, not defined in 10.2 */
    8889    { -10828, "Classic environment required but not available" }, /* kLSNoClassicEnvironmentErr, not defined in 10.2 */
     90    { -10829, "unable to launch multiple instances of application" }, /* kLSMultipleSessionsNotSupportedErr, not defined in 10.2 */
    8991#ifndef BROKEN_AUTHORIZATION
    9092    // Security framework errors
     
    9799    { icInternalErr, "internal Internet Config error" },
    98100    // Misc. errors
     101    { nsvErr, "the volume cannot be found (buggy filesystem?)" },
    99102    { procNotFound, "unable to connect to system service.\nAre you logged in?" },
    100103    { kCGErrorIllegalArgument, "window server error.\nAre you logged in?" },
     
    105108
    106109void usage() {
    107     fprintf(stderr, "usage: %s [-npswbmhCX] [-c creator] [-i bundleID] [-u URL] [-a name] [item ...] [-]\n"
    108                     "   or: %s [-npflswbmhCX] item ...\n", APP_NAME, APP_NAME);
     110    fprintf(stderr, "usage: %s [-npswbmhCXU] [-c creator] [-i bundleID] [-u URL] [-a name] [item ...] [-]\n"
     111                    "   or: %s [-npflswbmhCXU] item ...\n", APP_NAME, APP_NAME);
    109112    fprintf(stderr,
    110113        "  -n            print matching paths/URLs instead of opening them\n"
     
    121124        "  -C            force CFM/PEF Carbon application to launch in Classic\n"
    122125        "  -X            don't start Classic for this app if Classic isn't running\n"
     126        "  -U            interpret items as URLs, even if same-named files exist\n"
    123127        "  -c creator    match application by four-character creator code ('ToyS')\n"
    124128        "  -i bundle ID  match application by bundle identifier (com.apple.scripteditor)\n"
     
    127131        "'document' may be a file, folder, or disk - whatever the application can open.\n"
    128132        "'item' may be a file, folder, disk, or URL.\n\n");
    129     fprintf(stderr, "launch "VERSION" (c) 2001-03 Nicholas Riley <http://web.sabi.net/nriley/software/>.\n"
     133    fprintf(stderr, "launch "VERSION" (c) 2001-05 Nicholas Riley <http://web.sabi.net/nriley/software/>.\n"
    130134                    "Please send bugs, suggestions, etc. to <launchsw@sabi.net>.\n");
    131135
     
    341345    if (argc == 1) usage();
    342346   
    343     while ( (ch = getopt(argc, argv, "npflswbmhCXc:i:u:a:")) != -1) {
     347    while ( (ch = getopt(argc, argv, "npflswbmhCXUc:i:u:a:")) != -1) {
    344348        switch (ch) {
    345349        case 'n':
     
    384388        case 'C': LSPEC.launchFlags |= kLSLaunchInClassic; break;  // force Classic
    385389        case 'X': LSPEC.launchFlags ^= kLSLaunchStartClassic; break;// don't start Classic for app
     390        case 'U': OPTS.forceURLs = true; break;
    386391        case 'c':
    387392            if (strlen(optarg) != 4) errexit("creator (argument of -c) must be four characters long");
     
    390395            break;
    391396        case 'i':
    392             OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding());
     397            OPTS.bundleID = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
    393398            appSpecified = true;
    394399            break;
    395400        case 'a':
    396             OPTS.name = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding());
     401            OPTS.name = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
    397402            appSpecified = true;
    398403            break;
    399404        case 'u':
    400             { CFStringRef str = CFStringCreateWithCString(NULL, optarg, CFStringGetSystemEncoding());
     405            { CFStringRef str = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8);
    401406              LSPEC.appURL = CFURLCreateWithString(NULL, str, NULL);
    402407              if (str != NULL) CFRelease(str);
     
    471476                LSPEC.launchFlags ^= kLSLaunchAsync;
    472477            } else {
    473                 argStr = CFStringCreateWithCString(NULL, argv[i], CFStringGetSystemEncoding());
    474                 // check for URLs
    475                 itemURL = normalizedURLFromString(argStr);
    476                 if (itemURL == NULL && OPTS.action == ACTION_LAUNCH_URLS) {
    477                     // check for email addresses
    478                     if (strchr(argv[i], '@') != NULL && strchr(argv[i], '/') == NULL)
    479                         itemURL = normalizedURLFromPrefixSlack(CFSTR("mailto:"), argStr);
    480                     // check for "slack" URLs
    481                     if (itemURL == NULL && strchr(argv[i], '.') != NULL && strchr(argv[i], '/') != argv[i])
    482                         itemURL = normalizedURLFromPrefixSlack(CFSTR("http://"), argStr);
    483                 }
     478                struct stat stat_buf;
     479                if (!OPTS.forceURLs && stat(argv[i], &stat_buf) == 0) {
     480                    itemURL = NULL;
     481                } else {
     482                    argStr = CFStringCreateWithCString(NULL, argv[i], kCFStringEncodingUTF8);
     483                    // check for URLs
     484                    itemURL = normalizedURLFromString(argStr);
     485                    if (itemURL == NULL && OPTS.action == ACTION_LAUNCH_URLS) {
     486                        // check for email addresses
     487                        if (strchr(argv[i], '@') != NULL && strchr(argv[i], '/') == NULL)
     488                            itemURL = normalizedURLFromPrefixSlack(CFSTR("mailto:"), argStr);
     489                        // check for "slack" URLs
     490                        if (itemURL == NULL && strchr(argv[i], '.') != NULL && strchr(argv[i], '/') != argv[i])
     491                            itemURL = normalizedURLFromPrefixSlack(CFSTR("http://"), argStr);
     492                    }
     493                }
    484494                if (itemURL == NULL) {
    485495                    // check for file paths
    486                     itemURL = CFURLCreateWithFileSystemPath(NULL, argStr, kCFURLPOSIXPathStyle, false);
     496                    itemURL = CFURLCreateFromFileSystemRepresentation(NULL, argv[i], strlen(argv[i]), false);
    487497                    err = LSCopyItemInfoForURL(itemURL, kLSRequestExtensionFlagsOnly, &docInfo);
    488498                    if (err != noErr) osstatusexit(err, "unable to locate '%s'", argv[i]);
     
    496506}
    497507
     508Boolean stringFromURLIsRemote(CFURLRef url, char *strBuffer) {
     509    CFStringRef scheme = CFURLCopyScheme(url);
     510    Boolean isRemote = !CFEqual(scheme, CFSTR("file"));
     511    CFRelease(scheme);
     512   
     513    strBuffer[0] = '\0';
     514    if (isRemote) {
     515        CFStringRef urlString = CFURLGetString(url);
     516        CFStringGetCString(urlString, strBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
     517        CFRelease(urlString);
     518    } else {
     519        if (CFURLGetFileSystemRepresentation(url, false, strBuffer, STRBUF_LEN)) {
     520            if (strBuffer[0] == '.' && strBuffer[1] == '/') {
     521                // remove the leading "./"
     522                char *fromBufPtr = strBuffer + 2;
     523                while (true) {
     524                    *strBuffer = *fromBufPtr;
     525                    if (*fromBufPtr == '\0') break;
     526                    strBuffer++;
     527                    fromBufPtr++;
     528                }
     529            }
     530        } else {
     531            strcpy(strBuffer, "[can't get path: CFURLGetFileSystemRepresentation failed]");
     532        }
     533    }
     534    return isRemote;
     535}
     536
    498537void printPathFromURL(CFURLRef url, FILE *stream) {
    499     CFStringRef scheme, pathOrURL;
    500538    static char strBuffer[STRBUF_LEN];
    501    
    502539    check(url != NULL && stream != NULL);
    503 
    504     scheme = CFURLCopyScheme(url);
    505    
    506     if (CFEqual(scheme, CFSTR("file")))
    507         pathOrURL = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
    508     else
    509         pathOrURL = CFURLGetString(url);
    510 
    511     strBuffer[0] = '\0';
    512     CFStringGetCString(pathOrURL, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX buffer size issues?
     540    stringFromURLIsRemote(url, strBuffer);
    513541    fprintf(stream, "%s\n", strBuffer);
    514     CFRelease(scheme);
    515     CFRelease(pathOrURL);
    516542}
    517543
     
    582608
    583609    if (fscInfo.nodeFlags & kFSNodeIsDirectoryMask) {
    584         printf("\tcontents: %lu item%s\n", fscInfo.valence, fscInfo.valence != 1 ? "s" : "");
     610        printf("\tcontents: ");
     611        switch (fscInfo.valence) {
     612        case 0: printf("zero items\n"); break;
     613        case 1: printf("1 item\n"); break;
     614        default: printf("%lu items\n", fscInfo.valence);
     615        }
    585616    } else {
    586617        printSizes("data fork size", fscInfo.dataLogicalSize, fscInfo.dataPhysicalSize, true);
     
    605636}
    606637
     638const char *utf8StringFromCFStringRef(CFStringRef cfStr) {
     639    static char tmpBuffer[STRBUF_LEN];
     640    CFStringGetCString(cfStr, tmpBuffer, STRBUF_LEN, kCFStringEncodingUTF8);
     641    return tmpBuffer;
     642}
     643
     644const char *utf8StringFromOSType(OSType osType) {
     645    CFStringRef typeStr = CFStringCreateWithBytes(NULL, (const char *)&osType, 4, CFStringGetSystemEncoding(), false);
     646    if (typeStr == NULL) {
     647        // punt to displaying verbatim
     648        static char tmpBuffer[4];
     649        tmpBuffer[4] = '\0';
     650        strncpy(tmpBuffer, (const char *)&osType, 4);
     651        return tmpBuffer;
     652    }
     653    const char *buffer = utf8StringFromCFStringRef(typeStr);
     654    CFRelease(typeStr);
     655    return buffer;
     656}
     657
    607658// 'context' is to match prototype for CFArrayApplierFunction, it's unused
    608659void printInfoFromURL(CFURLRef url, void *context) {
    609     CFStringRef scheme, pathOrURL, kind;
    610     Boolean isRemote;
    611     static char strBuffer[STRBUF_LEN], tmpBuffer[STRBUF_LEN];
     660    CFStringRef kind;
     661    static char strBuffer[STRBUF_LEN];
    612662   
    613663    check(url != NULL && context == NULL);
    614664
    615     scheme = CFURLCopyScheme(url);
    616    
    617     isRemote = !CFEqual(scheme, CFSTR("file"));
    618     if (isRemote)
    619         pathOrURL = CFURLGetString(url);
    620     else
    621         pathOrURL = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
    622 
    623     strBuffer[0] = '\0';
    624     CFStringGetCString(pathOrURL, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX buffer size issues?
    625     if (isRemote)
     665    if (stringFromURLIsRemote(url, strBuffer))
    626666        printf("<%s>: URL\n", strBuffer);
    627667    else {
    628668        static LSItemInfoRecord info;
     669        CFStringRef version = NULL;
     670        UInt32 intVersion = 0;
    629671        OSStatus err = LSCopyItemInfoForURL(url, kLSRequestAllInfo, &info);
    630672        if (err != noErr) osstatusexit(err, "unable to get information about '%s'", strBuffer);
     
    655697        printf("\n");
    656698        if (!(info.flags & kLSItemInfoIsContainer) || info.flags & kLSItemInfoIsPackage) {
    657             tmpBuffer[4] = '\0';
    658             strncpy(tmpBuffer, (char *)&info.filetype, 4); printf("\ttype: '%s'", tmpBuffer);
    659             strncpy(tmpBuffer, (char *)&info.creator, 4); printf("\tcreator: '%s'\n", tmpBuffer);
     699            printf("\ttype: '%s'", utf8StringFromOSType(info.filetype));
     700            printf("\tcreator: '%s'\n", utf8StringFromOSType(info.creator));
    660701        }
    661702        if (info.flags & kLSItemInfoIsPackage || info.flags & kLSItemInfoIsApplication) {
     
    663704            CFBundleRef bundle = CFBundleCreate(NULL, url);
    664705            CFStringRef bundleID = NULL;
    665             CFStringRef appVersion = NULL;
    666             UInt32 intVersion = 0;
    667706            if (bundle == NULL && (info.flags & kLSItemInfoIsApplication)) {
    668707                FSRef fsr;
     
    683722                            CFPropertyListRef infoPlist = CFPropertyListCreateFromXMLData(NULL, plstData, kCFPropertyListImmutable, &error);
    684723                            if (plstData != NULL) {
    685                                 CFRelease(plstData);
    686                                 plstData = NULL;
     724                                CFRelease(plstData);
     725                                plstData = NULL;
     726                            } else {
     727                                // this function should handle the 'plst' 0 case too, but it doesn't provide error messages; however, it handles the case of an unbundled Mach-O binary, so it is useful as a fallback
     728                                infoPlist = CFBundleCopyInfoDictionaryForURL(url);
    687729                            }
    688730                            if (infoPlist == NULL) {
    689                                 CFStringGetCString(error, tmpBuffer, STRBUF_LEN, CFStringGetSystemEncoding());
    690                                 printf("\t['plst' 0 resource invalid: %s]\n", tmpBuffer);
     731                                printf("\t['plst' 0 resource invalid: %s]\n", utf8StringFromCFStringRef(error));
    691732                                CFRelease(error);
    692733                            } else {
     
    694735                                bundleID = CFDictionaryGetValue(infoPlist, kCFBundleIdentifierKey);
    695736                                if (bundleID != NULL) CFRetain(bundleID);
    696                                 appVersion = CFDictionaryGetValue(infoPlist, CFSTR("CFBundleShortVersionString"));
    697                                 if (appVersion == NULL)
    698                                     appVersion = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey);
    699                                 if (appVersion != NULL) CFRetain(appVersion);
     737                                version = CFDictionaryGetValue(infoPlist, CFSTR("CFBundleShortVersionString"));
     738                                if (version == NULL)
     739                                    version = CFDictionaryGetValue(infoPlist, kCFBundleVersionKey);
     740                                if (version != NULL) CFRetain(version);
    700741                                CFRelease(infoPlist);
    701742                            }
     
    705746                            if (err != noErr && err != resNotFound) osstatusexit(err, "unable to read 'vers' 1 resource");
    706747                        } else {
    707                             if (appVersion == NULL) { // prefer 'plst' version
    708                                 appVersion = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
     748                            if (version == NULL) { // prefer 'plst' version
     749                                version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
    709750                            }
    710751                            intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
     
    717758                if (bundleID != NULL) CFRetain(bundleID);
    718759                // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
    719                 appVersion = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
    720                 if (appVersion == NULL)
    721                     appVersion = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
    722                 if (appVersion != NULL) {
    723                     CFRetain(appVersion);
     760                version = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
     761                if (version == NULL)
     762                    version = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
     763                if (version != NULL) {
     764                    CFRetain(version);
    724765                    intVersion = CFBundleGetVersionNumber(bundle);
    725766                }
     
    727768            }
    728769            if (bundleID != NULL) {
    729                 CFStringGetCString(bundleID, tmpBuffer, STRBUF_LEN, CFStringGetSystemEncoding());
    730                 printf("\tbundle ID: %s\n", tmpBuffer);
     770                printf("\tbundle ID: %s\n", utf8StringFromCFStringRef(bundleID));
    731771                CFRelease(bundleID);
    732772            }
    733             if (appVersion != NULL) {
    734                 CFStringGetCString(appVersion, tmpBuffer, STRBUF_LEN, CFStringGetSystemEncoding());
    735                 printf("\tversion: %s", tmpBuffer);
    736                 if (intVersion != 0) printf(" [0x%lx = %lu]", intVersion, intVersion);
    737                 putchar('\n');
    738                 CFRelease(appVersion);
    739             }
    740         }
    741        
     773        } else {
     774            // try to get a version if we can, but don't complain if we can't
     775            FSRef fsr;
     776            if (CFURLGetFSRef(url, &fsr)) {
     777                SInt16 resFork = FSOpenResFile(&fsr, fsRdPerm);
     778                if (ResError() == noErr) {
     779                    VersRecHndl vers = (VersRecHndl)Get1Resource('vers', 1);
     780                    if (ResError() == noErr && vers != NULL) {
     781                        version = CFStringCreateWithPascalString(NULL, vers[0]->shortVersion, CFStringGetSystemEncoding()); // XXX use country code instead?
     782                        intVersion = ((NumVersionVariant)vers[0]->numericVersion).whole;
     783                    }
     784                }
     785                CloseResFile(resFork);
     786            }
     787        }
     788       
     789        if (version != NULL) {
     790            printf("\tversion: %s", utf8StringFromCFStringRef(version));
     791            if (intVersion != 0) printf(" [0x%lx = %lu]", intVersion, intVersion);
     792            putchar('\n');
     793            CFRelease(version);
     794        }
     795
    742796        // kind string
    743797        err = LSCopyKindStringForURL(url, &kind);
    744798        if (err != noErr) osstatusexit(err, "unable to get kind of '%s'", strBuffer);
    745         CFStringGetCString(kind, tmpBuffer, STRBUF_LEN, CFStringGetSystemEncoding());
    746         printf("\tkind: %s\n", tmpBuffer);
     799        printf("\tkind: %s\n", utf8StringFromCFStringRef(kind));
    747800        CFRelease(kind);
    748801        printMoreInfoFromURL(url);
    749802    }
    750     CFRelease(scheme);
    751     CFRelease(pathOrURL);
    752803}
    753804
     
    760811
    761812    strBuffer[0] = '\0';
    762     CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX buffer size issues?
     813    CFStringGetCString(urlStr, strBuffer, STRBUF_LEN, CFStringGetSystemEncoding()); // XXX no idea what encoding ICLaunchURL is supposed to take; leave as is for now
    763814    strStart = 0;
    764815    strEnd = strlen(strBuffer);
Note: See TracChangeset for help on using the changeset viewer.