// // ICeCoffEEServices.m // ICeCoffEE APE // // Created by Nicholas Riley on 5/10/05. // Copyright 2005 Nicholas Riley. All rights reserved. // #import "ICeCoffEEServices.h" #import "ICeCoffEEShared.h" // an approximate clone of HIToolbox's CreateServicesLocalizedDictKey static CFStringRef preferredLocalization(CFDictionaryRef localizations) { if (localizations == NULL) return NULL; CFIndex localizationCount = CFDictionaryGetCount(localizations); if (localizationCount == 0) return NULL; const void **locales = malloc(localizationCount * sizeof(void *)); if (locales == NULL) return NULL; CFDictionaryGetKeysAndValues(localizations, locales, NULL); CFArrayRef availableLocales = CFArrayCreate(NULL, locales, localizationCount, NULL); if (availableLocales == NULL) return NULL; // XXX need to obtain in-use app localization list, THEN canonicalize everything... // XXX THEN use CFBundleCopyLocalizationsForPreferences. All to fix RealPlayer. Aieee. CFArrayRef preferredLocales = CFBundleCopyPreferredLocalizationsFromArray(availableLocales); // NSLog(@"%@ => %@", availableLocales, preferredLocales); CFRelease(availableLocales); CFStringRef preferredLocalization; if (preferredLocales != NULL) { if (CFArrayGetCount(preferredLocales) > 0) { CFStringRef preferredLocale = CFArrayGetValueAtIndex(preferredLocales, 0); preferredLocalization = (CFStringRef)CFDictionaryGetValue(localizations, preferredLocale); } CFRelease(preferredLocales); } if (preferredLocalization == NULL) { preferredLocalization = (CFStringRef)CFDictionaryGetValue(localizations, CFSTR("default")); if (preferredLocalization == NULL) preferredLocalization = (CFStringRef)CFDictionaryGetValue(localizations, (CFStringRef)locales[0]); } free(locales); // NSLog(@"%@", preferredLocalization); return preferredLocalization; } NSArray *CFServiceControllerCopyServicesEntries(void); NSDictionary *ICCF_GetServicesInfo(void) { NSArray *services = CFServiceControllerCopyServicesEntries(); NSEnumerator *e = [services objectEnumerator]; NSDictionary *serviceEntry; NSMutableDictionary *serviceDict = [[NSMutableDictionary alloc] init]; while ( (serviceEntry = (NSDictionary *)[e nextObject]) != nil) { // XXX once tested, redo all this with CF, and no autoreleasing // XXX items named the same as submenus (figure out how Cocoa itself does it, too) NSString *itemPath = (NSString *)preferredLocalization((CFDictionaryRef)[serviceEntry objectForKey: @"NSMenuItem"]); if (itemPath == nil) continue; NSString *bundlePath = [serviceEntry objectForKey: @"NSBundlePath"]; BOOL bubbledUp = (bundlePath == nil); NSArray *itemComponents = [itemPath componentsSeparatedByString: @"/"]; NSEnumerator *ce = [itemComponents objectEnumerator]; NSString *itemComponent; NSMutableDictionary *levelDict = serviceDict; NSMutableDictionary *itemDict = nil; while ( (itemComponent = (NSString *)[ce nextObject]) != nil) { // levelDict is nil if just created if (levelDict != nil && !bubbledUp) { NSString *oldBundlePath = [itemDict objectForKey: (NSString *)kICServiceBundlePath]; if ([oldBundlePath isEqualToString: bundlePath]) bubbledUp = YES; else if (oldBundlePath != nil) { [oldBundlePath retain]; [itemDict removeObjectForKey: (NSString *)kICServiceBundlePath]; NSEnumerator *be = [levelDict objectEnumerator]; while ( (itemDict = (NSMutableDictionary *)[be nextObject]) != nil) [itemDict setObject: oldBundlePath forKey: (NSString *)kICServiceBundlePath]; [oldBundlePath release]; } } if (levelDict == nil) { levelDict = [[NSMutableDictionary alloc] init]; [itemDict setObject: levelDict forKey: (NSString *)kICServiceSubmenu]; [levelDict release]; itemDict = nil; } else { itemDict = [levelDict objectForKey: itemComponent]; } if (itemDict == nil) { itemDict = [[NSMutableDictionary alloc] init]; if (!bubbledUp) { [itemDict setObject: bundlePath forKey: (NSString *)kICServiceBundlePath]; bubbledUp = YES; } [levelDict setObject: itemDict forKey: itemComponent]; levelDict = nil; [itemDict release]; } else { levelDict = [itemDict objectForKey: (NSString *)kICServiceSubmenu]; } } if (!bubbledUp) [itemDict setObject: bundlePath forKey: (NSString *)kICServiceBundlePath]; NSString *keyEquivalent = (NSString *)preferredLocalization((CFDictionaryRef)[serviceEntry objectForKey: @"NSKeyEquivalent"]); if (keyEquivalent == nil) continue; [itemDict setObject: keyEquivalent forKey: (NSString *)kICServiceShortcut]; } [services release]; return [serviceDict autorelease]; } void ICCF_SetServicesMenu(NSMenu *servicesMenu) { // attaches a delegate (NSServiceMaster in 10.4, NSServiceMenuDelegate in 10.5) [[NSApplication sharedApplication] setServicesMenu: servicesMenu]; id delegate; if ( (delegate = [servicesMenu delegate]) == nil) return; // populate menu so we have something to filter; delegate reponds to one of these methods if ([delegate respondsToSelector: @selector(menuNeedsUpdate:)]) { [delegate menuNeedsUpdate: servicesMenu]; } if ([delegate respondsToSelector: @selector(menu:updateItem:atIndex:shouldCancel:)]) { int itemCount = [delegate numberOfItemsInMenu: servicesMenu]; for (int i = 0 ; i < itemCount ; i++) { [servicesMenu addItemWithTitle: @"" action: NULL keyEquivalent: @""]; } for (int i = 0 ; i < itemCount ; i++) { if (![delegate menu: servicesMenu updateItem: (NSMenuItem *)[servicesMenu itemAtIndex: i] atIndex: i shouldCancel: NO]) break; } } } IconRef ICCF_CopyIconRefForPath(NSString *path) { IconRef icon; FSRef fsr; SInt16 label; OSStatus err = noErr; err = FSPathMakeRef((const UInt8 *)[path fileSystemRepresentation], &fsr, NULL); if (err != noErr) return NULL; err = GetIconRefFromFileInfo(&fsr, 0, NULL, kFSCatInfoNone, NULL, kIconServicesNormalUsageFlag, &icon, &label); if (err != noErr) return NULL; return icon; }