/* * ICeCoffEEBookmarks.c * ICeCoffEE APE * * Created by Nicholas Riley on Sat Feb 01 2003. * Copyright (c) 2003 Nicholas Riley. All rights reserved. * */ #include "ICeCoffEEBookmarks.h" #include "ICeCoffEEConfig.h" // Make sure the encoding of this file is MacRoman, or this constant wonÕt work! #define kICBookmarkHelper "\pHelper¥bookmark" #define kICBookmarkScheme "bookmark" enum { kICBookmarkURLTooLongErr = 93800, kICBookmarkNameTooLongErr = 93801, kICBookmarkDescriptionTooLongErr = 93802, kICBookmarkNoHelperErr = 93803 }; CFURLRef ICCF_GetBookmarkHelperURL(ICInstance inst) { OSStatus err; CFURLRef helperURL; // try to get creator from Internet Config ICAttr junkAttr; ICAppSpec helperSpec; long helperSpecSize = sizeof(helperSpec); err = ICGetPref(inst, kICBookmarkHelper, &junkAttr, (void *)&helperSpec, &helperSpecSize); if (err == noErr && helperSpecSize == sizeof(helperSpec)) { err = LSFindApplicationForInfo(helperSpec.fCreator, NULL, NULL, NULL, &helperURL); if (err == noErr) return helperURL; } // try to use LaunchServices CFURLRef bookmarkURL = CFURLCreateWithString(NULL, CFSTR(kICBookmarkScheme":"), NULL); err = LSGetApplicationForURL(bookmarkURL, kLSRolesAll, NULL, &helperURL); if (bookmarkURL != NULL) CFRelease(bookmarkURL); if (err == noErr) return helperURL; return NULL; } static const ProcessSerialNumber kICNoProcess = {0, kNoProcess}; static OSStatus ICCF_GetPSNFromHelperURL(CFURLRef helperURL, ProcessSerialNumber *inPSN) { if (helperURL == NULL) return kICBookmarkNoHelperErr; FSRef helperFSR; if (!CFURLGetFSRef(helperURL, &helperFSR)) return kICBookmarkNoHelperErr; // locate running application ProcessSerialNumber psn = kICNoProcess; FSRef fsr; OSStatus err; while ( (err = GetNextProcess(&psn)) == noErr && psn.lowLongOfPSN != kNoProcess) { err = GetProcessBundleLocation(&psn, &fsr); if (err == noErr && FSCompareFSRefs(&fsr, &helperFSR) == noErr) { *inPSN = psn; return noErr; } } // launch application FSSpec fss; err = FSGetCatalogInfo(&helperFSR, kFSCatInfoNone, NULL, NULL, &fss, NULL); if (err != noErr) return err; LaunchParamBlockRec launchPB = { 0, 0, extendedBlock, extendedBlockLen, kNilOptions, launchNoFileFlags | launchContinue, &fss, kICNoProcess, 0, 0, 0, NULL }; err = LaunchApplication(&launchPB); if (err != noErr) { if (err == paramErr) return userCanceledErr; // if app force quit while launching return err; } *inPSN = launchPB.launchProcessSN; return noErr; } static OSStatus ICCF_AddBookmark(ICInstance inst, CFStringRef url, CFStringRef name, CFStringRef description) { if (url == NULL || CFStringGetLength(url) == 0) return noErr; CFURLRef helperURL = ICCF_GetBookmarkHelperURL(inst); if (helperURL == NULL) return kICBookmarkNoHelperErr; OSStatus err; ProcessSerialNumber psn; err = ICCF_GetPSNFromHelperURL(helperURL, &psn); CFRelease(helperURL); if (err != noErr) return err; if (psn.highLongOfPSN == kICNoProcess.highLongOfPSN && psn.lowLongOfPSN == kICNoProcess.lowLongOfPSN) return kICBookmarkNoHelperErr; AppleEvent event; char buf[1024]; // XXX make length a constant if (!CFStringGetCString(url, buf, sizeof(buf), kCFStringEncodingASCII)) return kICBookmarkURLTooLongErr; // XXX can I specify Unicode text ('utxt'?) here to get internationalization support? err = AEBuildAppleEvent('SURL', 'SURL', typeProcessSerialNumber, &psn, sizeof(psn), kAutoGenerateReturnID, kAnyTransactionID, &event, NULL, "'----':TEXT(@)", buf); if (err != noErr) return err; if (name != NULL) { if (!CFStringGetCString(name, buf, sizeof(buf), CFStringGetSystemEncoding())) return kICBookmarkNameTooLongErr; err = AEBuildParameters(&event, NULL, "'name':TEXT(@)", buf); if (err != noErr) return err; } if (description != NULL) { // XXX 256 hard-coded from limit of URL Manager Pro notes field if (!CFStringGetCString(description, buf, 256, CFStringGetSystemEncoding())) return kICBookmarkDescriptionTooLongErr; err = AEBuildParameters(&event, NULL, "'clip':TEXT(@)", buf); if (err != noErr) return err; } AppleEvent nullReply = {typeNull, nil}; err = AESend(&event, &nullReply, kAENoReply, kAENormalPriority, kNoTimeOut, nil, nil); (void)AEDisposeDesc(&event); if (err != noErr) return err; (void)AEDisposeDesc(&nullReply); // according to docs, don't call unless AESend returned successfully return noErr; } // XXX workaround for CFUserNotificationCreate log message in 10.2.4 at least #include #include #include // XXX end workaround OSStatus ICCF_DoBookmarkDialog(ICInstance inst, CFStringRef url) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 10, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (dict == NULL) return memFullErr; OSStatus err; CFURLRef bundleURL = CFBundleCopyBundleURL(ICCF_bundle); CFDictionaryAddValue(dict, kCFUserNotificationLocalizationURLKey, bundleURL); SAFE_RELEASE(bundleURL); const CFStringRef textFieldTitles[] = { ICCF_CopyLocalizedString(CFSTR("Location:")), ICCF_CopyLocalizedString(CFSTR("Name:")), ICCF_CopyLocalizedString(CFSTR("Description:")) }; const CFStringRef textFieldValues[] = { url, CFSTR(""), CFSTR("") }; CFArrayRef textFieldTitlesArray = CFArrayCreate(NULL, (const void **)&textFieldTitles, 3, &kCFTypeArrayCallBacks); CFArrayRef textFieldValuesArray = CFArrayCreate(NULL, (const void **)&textFieldValues, 3, &kCFTypeArrayCallBacks); CFDictionaryAddValue(dict, kCFUserNotificationAlertHeaderKey, ICCF_CopyLocalizedString(CFSTR("Add Bookmark"))); CFDictionaryAddValue(dict, kCFUserNotificationAlertMessageKey, ICCF_CopyLocalizedString(CFSTR("Bookmark name and description are optional."))); CFDictionaryAddValue(dict, kCFUserNotificationTextFieldTitlesKey, textFieldTitlesArray); CFDictionaryAddValue(dict, kCFUserNotificationTextFieldValuesKey, textFieldValuesArray); CFDictionaryAddValue(dict, kCFUserNotificationDefaultButtonTitleKey, ICCF_CopyLocalizedString(CFSTR("Add"))); CFDictionaryAddValue(dict, kCFUserNotificationAlternateButtonTitleKey, ICCF_CopyLocalizedString(CFSTR("Cancel"))); if (textFieldTitlesArray != NULL) CFRelease(textFieldTitlesArray); if (textFieldValuesArray != NULL) CFRelease(textFieldValuesArray); // XXX suppress log message from Apple's code: // 2003-02-02 17:48:26.896 TextEdit[2790] CFLog (20): Add Bookmark: Bookmark name and description are optional. int errfd = dup(STDERR_FILENO), nullfd = open("/dev/null", O_WRONLY, 0); // need to have something open in STDERR_FILENO because if it isn't, // CFLog will log to /dev/console dup2(nullfd, STDERR_FILENO); close(nullfd); CFUserNotificationRef notification = CFUserNotificationCreate(NULL, 0, kCFUserNotificationPlainAlertLevel, &err, dict); dup2(errfd, STDERR_FILENO); close(errfd); if (notification == NULL) { CFRelease(dict); return err; } CFOptionFlags responseFlags; CFUserNotificationReceiveResponse(notification, 0, &responseFlags); if (responseFlags != kCFUserNotificationDefaultResponse) return userCanceledErr; CFDictionaryRef responseDict = CFUserNotificationGetResponseDictionary(notification); if (responseDict == NULL) return userCanceledErr; textFieldValuesArray = CFDictionaryGetValue(responseDict, kCFUserNotificationTextFieldValuesKey); if (textFieldValuesArray == NULL) return userCanceledErr; url = CFArrayGetValueAtIndex(textFieldValuesArray, 0); CFStringRef name = CFArrayGetValueAtIndex(textFieldValuesArray, 1); CFStringRef description = CFArrayGetValueAtIndex(textFieldValuesArray, 2); err = ICCF_AddBookmark(inst, url, name, description); SAFE_RELEASE(url); SAFE_RELEASE(name); SAFE_RELEASE(description); return err; }