source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEAction.c @ 465

Last change on this file since 465 was 465, checked in by Nicholas Riley, 11 years ago

Don't complain if previously-displayed helper app disappears.

File size: 12.6 KB
Line 
1/*
2 *  ICeCoffEEAction.c
3 *  ICeCoffEE APE
4 *
5 *  Created by Nicholas Riley on Wed Jan 29 2003.
6 *  Copyright (c) 2003 Nicholas Riley. All rights reserved.
7 *
8 */
9
10#include "ICeCoffEEAction.h"
11#include "ICeCoffEEConfig.h"
12#include "ICeCoffEEShared.h"
13#include "ICeCoffEEBookmarks.h"
14#include "ICeCoffEESharing.h"
15
16#define THROW_ERR(e) { err = e; goto END; }
17
18static CFStringRef ICCF_NameWithLocation(CFStringRef name, CFURLRef url) {
19    CFURLRef urlMinus = CFURLCreateCopyDeletingLastPathComponent(NULL, url);
20    CFStringRef urlString = CFURLCopyFileSystemPath(urlMinus, kCFURLPOSIXPathStyle);
21    CFStringRef nameWithLocationTemplate = ICCF_CopyLocalizedString(CFSTR("App%@Location%@"));
22    CFStringRef nameWithLocation = CFStringCreateWithFormat(NULL, NULL, nameWithLocationTemplate, name, urlString);
23   
24    SAFE_RELEASE(nameWithLocationTemplate);
25    SAFE_RELEASE(urlMinus);
26    SAFE_RELEASE(urlString);
27    return nameWithLocation;
28}
29
30static const MenuItemIndex kICAppMenuItemHasPath = 0xfffe;
31
32typedef struct {
33    CFURLRef defaultAppURL; // URL of default app, set to NULL after added to menu
34    CFMutableSetRef appPaths;
35    CFMutableDictionaryRef appItemTitles; // keys: URLs; values: item titles (CFString)
36    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)
37    MenuRef menu;
38} icAppMenuContext;
39
40static OSStatus ICCF_AddAppItemTitle(icAppMenuContext *ctx, CFURLRef appURL) {
41    CFStringRef appName = NULL, appItemTitle = NULL;
42    CFBundleRef appBundle = NULL;
43    OSStatus err = noErr;
44
45    CFStringRef appPath = CFURLCopyPath(appURL);
46    // only one entry for each path
47    if (CFSetContainsValue(ctx->appPaths, appPath))
48        return dupFNErr;
49    CFSetAddValue(ctx->appPaths, appPath);
50
51    if ( (err = LSCopyDisplayNameForURL(appURL, &appName)) != noErr)
52        return err;
53
54    // if we encounter multiple applications with the same display name, add locations to the menu item titles to disambiguate them
55    CFURLRef sameAppURL;
56    Boolean shouldAppendLocation;
57    if ( (shouldAppendLocation = CFDictionaryGetValueIfPresent(ctx->appURLs, appName, (const void **)&sameAppURL)) && (CFTypeRef)sameAppURL != kCFNull) {
58        // this app is the second encountered with the same name; go back and fix the menu item title of the first app
59        CFStringRef sameAppItemTitle = CFDictionaryGetValue(ctx->appItemTitles, sameAppURL);
60        CFStringRef appItemTitleWithVersion = ICCF_NameWithLocation(sameAppItemTitle, sameAppURL);
61        CFDictionarySetValue(ctx->appItemTitles, sameAppURL, appItemTitleWithVersion);
62        SAFE_RELEASE(appItemTitleWithVersion);
63        CFDictionarySetValue(ctx->appURLs, appName, kCFNull);
64    }
65
66    CFRetain(appName);
67    appItemTitle = appName;
68
69    if ( (appBundle = CFBundleCreate(NULL, appURL)) != NULL) {
70        // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
71        CFStringRef appVersion = CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("CFBundleShortVersionString"));
72        if (appVersion == NULL)
73            appVersion = CFBundleGetValueForInfoDictionaryKey(appBundle, kCFBundleVersionKey);
74        if (appVersion != NULL) {
75            appItemTitle = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), appName, appVersion);
76            CFRelease(appName);
77        }
78        CFRelease(appBundle);
79    }
80
81    if (shouldAppendLocation) {
82        CFStringRef appItemTitleWithVersion = ICCF_NameWithLocation(appItemTitle, appURL);
83        CFRelease(appItemTitle);
84        appItemTitle = appItemTitleWithVersion;
85    } else {
86        CFDictionarySetValue(ctx->appURLs, appName, appURL);
87    }
88
89    if (ctx->defaultAppURL != NULL && CFEqual(appURL, ctx->defaultAppURL)) {
90        CFStringRef defaultFormat = ICCF_CopyLocalizedString(CFSTR("DefaultApp%@"));
91        CFStringRef appItemTitleWithDefault = CFStringCreateWithFormat(NULL, NULL, defaultFormat, appItemTitle);
92        CFRelease(appItemTitle);
93        appItemTitle = appItemTitleWithDefault;
94        ctx->defaultAppURL = NULL; // mark as added
95    }
96
97    CFDictionarySetValue(ctx->appItemTitles, appURL, appItemTitle);
98    CFRelease(appItemTitle);
99
100    return noErr;
101}
102
103static OSStatus ICCF_AddTitledAppToMenu(icAppMenuContext *ctx, CFURLRef appURL, MenuCommand menuCommand) {
104    CFStringRef appItemTitle = NULL;
105    IconRef appIcon = NULL;
106    FSRef appFSR;
107    SInt16 label;
108    MenuItemIndex menuItemIndex;
109    OSStatus err = noErr;
110
111    appItemTitle = CFDictionaryGetValue(ctx->appItemTitles, appURL);
112    if (appItemTitle == NULL) return fnfErr;
113   
114    err = AppendMenuItemTextWithCFString(ctx->menu, appItemTitle, 0, menuCommand, &menuItemIndex);
115    if (err != noErr) return err;
116
117    if (!CFURLGetFSRef(appURL, &appFSR)) return paramErr;
118    err = GetIconRefFromFileInfo(&appFSR, 0, NULL, kFSCatInfoNone, NULL, kIconServicesNormalUsageFlag, &appIcon, &label);
119    if (err != noErr) return err;
120
121    SetMenuItemIconHandle(ctx->menu, menuItemIndex, kMenuIconRefType, (Handle)appIcon);
122    SetMenuItemRefCon(ctx->menu, menuItemIndex, (UInt32)appURL);
123    ReleaseIconRef(appIcon);
124
125    return err;
126}
127
128static OSStatus ICCF_AddAppToMenu(icAppMenuContext *ctx, CFURLRef appURL, MenuCommand menuCommand) {
129    OSStatus err = ICCF_AddAppItemTitle(ctx, appURL);
130    switch (err) {
131        case noErr: break;
132        case fnfErr: return noErr;
133        case dupFNErr: return noErr;
134        default: return err;
135    }
136    return ICCF_AddTitledAppToMenu(ctx, appURL, menuCommand);
137}   
138
139CFComparisonResult ICCF_CompareURLsByItemTitle(const void *url1, const void *url2, void *appItemTitles) {
140    CFStringRef appItemTitle1 = CFDictionaryGetValue((CFDictionaryRef)appItemTitles, (CFURLRef)url1);
141    CFStringRef appItemTitle2 = CFDictionaryGetValue((CFDictionaryRef)appItemTitles, (CFURLRef)url2);
142    return CFStringCompareWithOptions(appItemTitle1, appItemTitle2,
143                                      CFRangeMake(0, CFStringGetLength(appItemTitle1)),
144                                      kCFCompareCaseInsensitive | kCFCompareNumerically);
145}
146
147enum {
148    kICURLActionOpenWith = 'OpnW',
149    kICURLActionAddBookmark = 'AddB',
150    kICURLActionCopy = 'Copy'
151};
152
153OSStatus ICCF_DoURLActionMenu(ICInstance inst, CFURLRef url, LSLaunchFlags launchFlags) {
154    CFArrayRef appURLsUnsorted = NULL; // matching app URLs
155    CFMutableArrayRef appURLs = NULL; // matching app URLs sorted by item title
156    CFArrayRef urlArray = NULL; // single-URL array
157    icAppMenuContext ctx = {NULL, NULL, NULL, NULL};
158    OSStatus err;
159   
160    appURLsUnsorted = LSCopyApplicationURLsForURL(url, kLSRolesAll);
161
162    CFIndex appCount = 0;
163    if (appURLsUnsorted == NULL || (appCount = CFArrayGetCount(appURLsUnsorted)) == 0)
164        THROW_ERR(kLSApplicationNotFoundErr);
165
166    if ( (appURLs = CFArrayCreateMutableCopy(NULL, appCount, appURLsUnsorted)) == NULL)
167        THROW_ERR(memFullErr);
168
169    if ( (ctx.appPaths = CFSetCreateMutable(NULL, appCount, &kCFCopyStringSetCallBacks)) == NULL)
170        THROW_ERR(memFullErr);
171
172    if ( (ctx.appItemTitles = CFDictionaryCreateMutable(NULL, appCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL)
173        THROW_ERR(memFullErr);
174
175    if ( (ctx.appURLs = CFDictionaryCreateMutable(NULL, appCount, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL)
176        THROW_ERR(memFullErr);
177
178    LSGetApplicationForURL(url, kLSRolesAll, NULL, &ctx.defaultAppURL);
179
180    CFIndex appIndex;
181    CFURLRef appURL;
182    for (appIndex = 0 ; appIndex < appCount ; appIndex++) {
183        appURL = CFArrayGetValueAtIndex(appURLs, appIndex);
184        err = ICCF_AddAppItemTitle(&ctx, appURL);
185        switch (err) {
186            case noErr: break;
187            case dupFNErr:
188                CFArrayRemoveValueAtIndex(appURLs, appIndex);
189                appIndex--;
190                appCount--;
191                break;
192            default:
193                THROW_ERR(err);
194        } 
195    }
196
197    CFArraySortValues(appURLs, CFRangeMake(0, appCount), ICCF_CompareURLsByItemTitle, ctx.appItemTitles);
198   
199    if ( (err = CreateNewMenu(0, kMenuAttrExcludesMarkColumn, &ctx.menu)) != noErr)
200        THROW_ERR(err);
201   
202    MenuItemIndex menuItemIndex;
203    if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Open Location With")), kMenuItemAttrDisabled, 0, &menuItemIndex)) != noErr)
204        THROW_ERR(err);
205
206    if ( (urlArray = CFArrayCreate(NULL, (const void **)&url, 1, &kCFTypeArrayCallBacks)) == NULL)
207        THROW_ERR(memFullErr);
208
209    for (appIndex = 0 ; appIndex < appCount ; appIndex++) {
210        appURL = CFArrayGetValueAtIndex(appURLs, appIndex);
211
212        if ( (err = ICCF_AddTitledAppToMenu(&ctx, appURL, kICURLActionOpenWith)) != noErr)
213            THROW_ERR(err);
214    }
215    // sometimes the default protocol handler won't be on the list because it doesnÕt claim to handle that protocol; add it anyway
216    if (ctx.defaultAppURL != NULL) {
217        if ( (err = ICCF_AddAppToMenu(&ctx, ctx.defaultAppURL, kICURLActionOpenWith)) != noErr)
218            THROW_ERR(err);
219    }
220
221    appURL = ICCF_GetBookmarkHelperURL(inst);
222    if (appURL != NULL) {
223        if ( (err = AppendMenuItemTextWithCFString(ctx.menu, CFSTR(""), kMenuItemAttrSeparator, 0, NULL)) != noErr)
224            THROW_ERR(err);
225        if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Add Bookmark")), kMenuItemAttrDisabled, 0, &menuItemIndex)) != noErr)
226            THROW_ERR(err);
227        // 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
228        if ( (err = ICCF_AddAppToMenu(&ctx, appURL, kICURLActionAddBookmark)) != noErr)
229            THROW_ERR(err);
230    }
231
232    if ( (err = AppendMenuItemTextWithCFString(ctx.menu, CFSTR(""), kMenuItemAttrSeparator, 0, NULL)) != noErr)
233        THROW_ERR(err);
234    if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Copy Link")), 0, kICURLActionCopy, NULL)) != noErr)
235        THROW_ERR(err);
236
237    InsertMenu(ctx.menu, -1);
238    Point mousePoint;
239    GetGlobalMouse(&mousePoint);
240    long selectedAppIndex = PopUpMenuSelect(ctx.menu, mousePoint.v + 18, mousePoint.h - 30, 0/*popUpItem*/);
241    if (selectedAppIndex == 0) {
242        err = userCanceledErr;
243    } else {
244        CFURLRef appURL = NULL;
245        MenuCommand menuCommand;
246        err = GetMenuItemRefCon(ctx.menu, selectedAppIndex, (void *)&appURL);
247        if (err == noErr) {
248            err = GetMenuItemCommandID(ctx.menu, selectedAppIndex, &menuCommand);
249            if (err == noErr) {
250                if (menuCommand == kICURLActionOpenWith) {
251                    LSLaunchURLSpec lsSpec = {appURL, urlArray, NULL, launchFlags};
252                    err = LSOpenFromURLSpec(&lsSpec, NULL);
253                } else if (menuCommand == kICURLActionAddBookmark) {
254                    err = ICCF_DoBookmarkDialog(inst, CFURLGetString(url));
255                } else if (menuCommand == kICURLActionCopy) {
256                    err = ICCF_CopyLink(CFURLGetString(url));
257                }
258            }
259        }
260    }
261   
262END:
263    if (ctx.menu != NULL) {
264        DeleteMenu(GetMenuID(ctx.menu));
265        DisposeMenu(ctx.menu);
266    }
267    SAFE_RELEASE(urlArray);
268    SAFE_RELEASE(appURLsUnsorted);
269    SAFE_RELEASE(appURLs);
270    SAFE_RELEASE(ctx.appPaths);
271    SAFE_RELEASE(ctx.appItemTitles);
272    SAFE_RELEASE(ctx.appURLs);
273   
274    return err;
275}
276
277OSStatus ICCF_DoURLActionLaunch(ICInstance inst, CFURLRef url, LSLaunchFlags launchFlags) {
278    CFArrayRef urlArray = CFArrayCreate(NULL, (const void **)&url, 1, &kCFTypeArrayCallBacks);
279    LSLaunchURLSpec lsSpec = {NULL, urlArray, NULL, launchFlags, NULL};
280    OSStatus err;
281
282    if (urlArray == NULL)
283        THROW_ERR(memFullErr);
284
285    err = LSOpenFromURLSpec(&lsSpec, NULL);
286
287END:
288    SAFE_RELEASE(urlArray);
289
290    return err;
291}
292
293OSStatus ICCF_DoURLAction(ICInstance inst, ConstStr255Param hint, const char *urlData, long startIndex, long endIndex, iccfURLAction action) {
294    Handle h = NewHandle(0);
295    CFURLRef url = NULL;
296    LSLaunchFlags launchFlags = kLSLaunchDefaults;
297    OSStatus err;
298
299    if (h == NULL) return MemError();
300
301    if ( (err = ICParseURL(inst, hint, urlData + startIndex, endIndex - startIndex + 1,
302                           &startIndex, &endIndex, h)) != noErr) THROW_ERR(err);
303
304    if ( (url = CFURLCreateWithBytes(NULL, (const UInt8 *)*h, GetHandleSize(h), kCFStringEncodingASCII,
305                                     NULL)) == NULL) THROW_ERR(paramErr);
306
307    if (action.launchInBackground) launchFlags |= kLSLaunchDontSwitch;
308
309    if (action.presentMenu)
310        err = ICCF_DoURLActionMenu(inst, url, launchFlags);
311    else
312        err = ICCF_DoURLActionLaunch(inst, url, launchFlags);
313
314END:
315    if (h != NULL) DisposeHandle(h);
316    SAFE_RELEASE(url);
317
318    return err;
319}
Note: See TracBrowser for help on using the repository browser.