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

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

Initial import.

File size: 26.1 KB
RevLine 
[3]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.