/* * ICeCoffEEAction.c * ICeCoffEE APE * * Created by Nicholas Riley on Wed Jan 29 2003. * Copyright (c) 2003 Nicholas Riley. All rights reserved. * */ #include "ICeCoffEEAction.h" #include "ICeCoffEEConfig.h" #include "ICeCoffEEShared.h" #include "ICeCoffEEBookmarks.h" #define THROW_ERR(e) { err = e; goto END; } static CFStringRef ICCF_NameWithLocation(CFStringRef name, CFURLRef url) { CFURLRef urlMinus = CFURLCreateCopyDeletingLastPathComponent(NULL, url); CFStringRef urlString = CFURLCopyFileSystemPath(urlMinus, kCFURLPOSIXPathStyle); CFStringRef nameWithLocationTemplate = ICCF_CopyLocalizedString(CFSTR("App%@Location%@")); CFStringRef nameWithLocation = CFStringCreateWithFormat(NULL, NULL, nameWithLocationTemplate, name, urlString); SAFE_RELEASE(nameWithLocationTemplate); SAFE_RELEASE(urlMinus); SAFE_RELEASE(urlString); return nameWithLocation; } static const MenuItemIndex kICAppMenuItemHasPath = 0xfffe; typedef struct { CFURLRef defaultAppURL; // URL of default app, set to NULL after added to menu CFMutableSetRef appPaths; CFMutableDictionaryRef appItemTitles; // keys: URLs; values: item titles (CFString) CFMutableDictionaryRef appURLs; // keys: app display names (CFString), values: URL of item without path appended, or NULL if already appended (at least 2 items with this name exist) MenuRef menu; } icAppMenuContext; static OSStatus ICCF_AddAppItemTitle(icAppMenuContext *ctx, CFURLRef appURL) { CFStringRef appName = NULL, appItemTitle = NULL; CFBundleRef appBundle = NULL; OSStatus err = noErr; CFStringRef appPath = CFURLCopyPath(appURL); // only one entry for each path if (CFSetContainsValue(ctx->appPaths, appPath)) return dupFNErr; CFSetAddValue(ctx->appPaths, appPath); if ( (err = LSCopyDisplayNameForURL(appURL, &appName)) != noErr) return err; // if we encounter multiple applications with the same display name, add locations to the menu item titles to disambiguate them CFURLRef sameAppURL; Boolean shouldAppendLocation; if ( (shouldAppendLocation = CFDictionaryGetValueIfPresent(ctx->appURLs, appName, (const void **)&sameAppURL)) && (CFTypeRef)sameAppURL != kCFNull) { // this app is the second encountered with the same name; go back and fix the menu item title of the first app CFStringRef sameAppItemTitle = CFDictionaryGetValue(ctx->appItemTitles, sameAppURL); CFStringRef appItemTitleWithVersion = ICCF_NameWithLocation(sameAppItemTitle, sameAppURL); CFDictionarySetValue(ctx->appItemTitles, sameAppURL, appItemTitleWithVersion); SAFE_RELEASE(appItemTitleWithVersion); CFDictionarySetValue(ctx->appURLs, appName, kCFNull); } CFRetain(appName); appItemTitle = appName; if ( (appBundle = CFBundleCreate(NULL, appURL)) != NULL) { // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari CFStringRef appVersion = CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("CFBundleShortVersionString")); if (appVersion == NULL) appVersion = CFBundleGetValueForInfoDictionaryKey(appBundle, kCFBundleVersionKey); if (appVersion != NULL) { appItemTitle = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), appName, appVersion); CFRelease(appName); } CFRelease(appBundle); } if (shouldAppendLocation) { CFStringRef appItemTitleWithVersion = ICCF_NameWithLocation(appItemTitle, appURL); CFRelease(appItemTitle); appItemTitle = appItemTitleWithVersion; } else { CFDictionarySetValue(ctx->appURLs, appName, appURL); } if (ctx->defaultAppURL != NULL && CFEqual(appURL, ctx->defaultAppURL)) { CFStringRef defaultFormat = ICCF_CopyLocalizedString(CFSTR("DefaultApp%@")); CFStringRef appItemTitleWithDefault = CFStringCreateWithFormat(NULL, NULL, defaultFormat, appItemTitle); CFRelease(appItemTitle); appItemTitle = appItemTitleWithDefault; ctx->defaultAppURL = NULL; // mark as added } CFDictionarySetValue(ctx->appItemTitles, appURL, appItemTitle); CFRelease(appItemTitle); return noErr; } static OSStatus ICCF_AddTitledAppToMenu(icAppMenuContext *ctx, CFURLRef appURL, MenuCommand menuCommand) { CFStringRef appItemTitle = NULL; IconRef appIcon = NULL; FSRef appFSR; SInt16 label; MenuItemIndex menuItemIndex; OSStatus err = noErr; appItemTitle = CFDictionaryGetValue(ctx->appItemTitles, appURL); if (appItemTitle == NULL) return fnfErr; err = AppendMenuItemTextWithCFString(ctx->menu, appItemTitle, 0, 0, &menuItemIndex); if (err != noErr) return err; if (!CFURLGetFSRef(appURL, &appFSR)) return paramErr; err = GetIconRefFromFileInfo(&appFSR, 0, NULL, kFSCatInfoNone, NULL, kIconServicesNormalUsageFlag, &appIcon, &label); if (err != noErr) return err; SetMenuItemIconHandle(ctx->menu, menuItemIndex, kMenuIconRefType, (Handle)appIcon); SetMenuItemCommandID(ctx->menu, menuItemIndex, menuCommand); SetMenuItemRefCon(ctx->menu, menuItemIndex, (UInt32)appURL); ReleaseIconRef(appIcon); return err; } static OSStatus ICCF_AddAppToMenu(icAppMenuContext *ctx, CFURLRef appURL, MenuCommand menuCommand) { OSStatus err = ICCF_AddAppItemTitle(ctx, appURL); switch (err) { case noErr: break; case dupFNErr: return noErr; default: return err; } return ICCF_AddTitledAppToMenu(ctx, appURL, menuCommand); } CFComparisonResult ICCF_CompareURLsByItemTitle(const void *url1, const void *url2, void *appItemTitles) { CFStringRef appItemTitle1 = CFDictionaryGetValue((CFDictionaryRef)appItemTitles, (CFURLRef)url1); CFStringRef appItemTitle2 = CFDictionaryGetValue((CFDictionaryRef)appItemTitles, (CFURLRef)url2); return CFStringCompareWithOptions(appItemTitle1, appItemTitle2, CFRangeMake(0, CFStringGetLength(appItemTitle1)), kCFCompareCaseInsensitive | kCFCompareNumerically); } enum { kICURLActionOpenWith = 'OpnW', kICURLActionAddBookmark = 'AddB' }; OSStatus ICCF_DoURLActionMenu(ICInstance inst, CFURLRef url, LSLaunchFlags launchFlags) { CFArrayRef appURLsUnsorted = NULL; // matching app URLs CFMutableArrayRef appURLs = NULL; // matching app URLs sorted by item title CFArrayRef urlArray = NULL; // single-URL array icAppMenuContext ctx = {NULL, NULL, NULL, NULL}; OSStatus err; appURLsUnsorted = LSCopyApplicationURLsForURL(url, kLSRolesAll); CFIndex appCount = 0; if (appURLsUnsorted == NULL || (appCount = CFArrayGetCount(appURLsUnsorted)) == 0) THROW_ERR(kLSApplicationNotFoundErr); if ( (appURLs = CFArrayCreateMutableCopy(NULL, appCount, appURLsUnsorted)) == NULL) THROW_ERR(memFullErr); if ( (ctx.appPaths = CFSetCreateMutable(NULL, appCount, &kCFCopyStringSetCallBacks)) == NULL) THROW_ERR(memFullErr); if ( (ctx.appItemTitles = CFDictionaryCreateMutable(NULL, appCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL) THROW_ERR(memFullErr); if ( (ctx.appURLs = CFDictionaryCreateMutable(NULL, appCount, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL) THROW_ERR(memFullErr); LSGetApplicationForURL(url, kLSRolesAll, NULL, &ctx.defaultAppURL); CFIndex appIndex; CFURLRef appURL; for (appIndex = 0 ; appIndex < appCount ; appIndex++) { appURL = CFArrayGetValueAtIndex(appURLs, appIndex); err = ICCF_AddAppItemTitle(&ctx, appURL); switch (err) { case noErr: break; case dupFNErr: CFArrayRemoveValueAtIndex(appURLs, appIndex); appIndex--; appCount--; break; default: THROW_ERR(err); } } CFArraySortValues(appURLs, CFRangeMake(0, appCount), ICCF_CompareURLsByItemTitle, ctx.appItemTitles); if ( (err = CreateNewMenu(0, kMenuAttrExcludesMarkColumn, &ctx.menu)) != noErr) THROW_ERR(err); MenuItemIndex menuItemIndex; if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Open Location With")), kMenuItemAttrDisabled, 0, &menuItemIndex)) != noErr) THROW_ERR(err); if ( (urlArray = CFArrayCreate(NULL, (const void **)&url, 1, &kCFTypeArrayCallBacks)) == NULL) THROW_ERR(memFullErr); for (appIndex = 0 ; appIndex < appCount ; appIndex++) { appURL = CFArrayGetValueAtIndex(appURLs, appIndex); if ( (err = ICCF_AddTitledAppToMenu(&ctx, appURL, kICURLActionOpenWith)) != noErr) THROW_ERR(err); } // sometimes the default protocol handler won't be on the list because it doesnŐt claim to handle that protocol; add it anyway if (ctx.defaultAppURL != NULL) { if ( (err = ICCF_AddAppToMenu(&ctx, ctx.defaultAppURL, kICURLActionOpenWith)) != noErr) THROW_ERR(err); } appURL = ICCF_GetBookmarkHelperURL(inst); if (appURL != NULL) { if ( (err = AppendMenuItemTextWithCFString(ctx.menu, CFSTR(""), kMenuItemAttrSeparator, 0, NULL)) != noErr) THROW_ERR(err); if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Add Bookmark")), kMenuItemAttrDisabled, 0, &menuItemIndex)) != noErr) THROW_ERR(err); // the app won't show up at the bottom if someone cmd-option-clicks on 'bookmark:', but they'd have to be crazy to do that anyway if ( (err = ICCF_AddAppToMenu(&ctx, appURL, kICURLActionAddBookmark)) != noErr) THROW_ERR(err); } InsertMenu(ctx.menu, -1); Point mousePoint; GetGlobalMouse(&mousePoint); long selectedAppIndex = PopUpMenuSelect(ctx.menu, mousePoint.v + 18, mousePoint.h - 30, 0/*popUpItem*/); if (selectedAppIndex == 0) { err = userCanceledErr; } else { CFURLRef appURL = NULL; MenuCommand menuCommand; err = GetMenuItemRefCon(ctx.menu, selectedAppIndex, (void *)&appURL); if (err == noErr) { err = GetMenuItemCommandID(ctx.menu, selectedAppIndex, &menuCommand); if (err == noErr) { if (menuCommand == kICURLActionOpenWith) { LSLaunchURLSpec lsSpec = {appURL, urlArray, NULL, launchFlags}; err = LSOpenFromURLSpec(&lsSpec, NULL); } else if (menuCommand == kICURLActionAddBookmark) { err = ICCF_DoBookmarkDialog(inst, CFURLGetString(url)); } } } } END: if (ctx.menu != NULL) { DeleteMenu(GetMenuID(ctx.menu)); DisposeMenu(ctx.menu); } SAFE_RELEASE(urlArray); SAFE_RELEASE(appURLsUnsorted); SAFE_RELEASE(appURLs); SAFE_RELEASE(ctx.appPaths); SAFE_RELEASE(ctx.appItemTitles); SAFE_RELEASE(ctx.appURLs); return err; } OSStatus ICCF_DoURLActionLaunch(ICInstance inst, CFURLRef url, LSLaunchFlags launchFlags) { CFArrayRef urlArray = CFArrayCreate(NULL, (const void **)&url, 1, &kCFTypeArrayCallBacks); LSLaunchURLSpec lsSpec = {NULL, urlArray, NULL, launchFlags, NULL}; OSStatus err; if (urlArray == NULL) THROW_ERR(memFullErr); err = LSOpenFromURLSpec(&lsSpec, NULL); END: SAFE_RELEASE(urlArray); return err; } OSStatus ICCF_DoURLAction(ICInstance inst, ConstStr255Param hint, const char *urlData, long startIndex, long endIndex, iccfURLAction action) { Handle h = NewHandle(0); CFURLRef url = NULL; LSLaunchFlags launchFlags = kLSLaunchDefaults; OSStatus err; if (h == NULL) return MemError(); if ( (err = ICParseURL(inst, hint, urlData + startIndex, endIndex - startIndex + 1, &startIndex, &endIndex, h)) != noErr) THROW_ERR(err); if ( (url = CFURLCreateWithBytes(NULL, (const UInt8 *)*h, GetHandleSize(h), kCFStringEncodingASCII, NULL)) == NULL) THROW_ERR(paramErr); if (action.launchInBackground) launchFlags |= kLSLaunchDontSwitch; if (action.presentMenu) err = ICCF_DoURLActionMenu(inst, url, launchFlags); else err = ICCF_DoURLActionLaunch(inst, url, launchFlags); END: if (h != NULL) DisposeHandle(h); SAFE_RELEASE(url); return err; }