source: trunk/ICeCoffEE/ICeCoffEE/APEMain.m @ 389

Last change on this file since 389 was 389, checked in by Nicholas Riley, 13 years ago

Remove unnecessary CPS usage and CFBundleGetBundleWithIdentifier.

File size: 13.0 KB
Line 
1// ===========================================================================
2//
3//      File:           APEMain.m
4//
5//      Contains:       ICeCoffEE APE Module code
6//
7//      Copyright:      Copyright (c) 2003, Nicholas Riley
8//                      All Rights Reserved.
9//
10//      Author(s):      Nicholas Riley (Sun Jan 19 2003)
11//
12// ===========================================================================
13
14#import <Carbon/Carbon.h>
15#import <ApplicationEnhancer/ApplicationEnhancer.h>
16#import <ApplicationEnhancer/APETools.h>
17#import <objc/objc-runtime.h>
18#import "ICeCoffEE.h"
19
20//¥¥¥ Our settings
21
22//¥¥¥ Function prototypes
23static void ICCF_ReloadPrefs();                         // reloads our preferences
24static void ICCF_MigratePrefs();                        // migrates prefs from 1.0Ð1.2
25
26//¥¥¥ Enter sandman, the code begins --
27
28#define ICCF_GET_PATCHCLASS(patchclass) \
29    struct objc_class *patchclass = objc_getClass(patchclass ## Name); \
30    if (patchclass == NULL) { \
31        ICapeprintf("can't get %s\n", patchclass ## Name); \
32        return NO; \
33    }
34
35#define ICCF_GET_METHOD(name, patchclass, sel) \
36    Method name = class_getInstanceMethod(patchclass, sel); \
37    if (name == NULL) { \
38        ICapeprintf("can't get %s\n", patchclass ## Name); \
39        return NO; \
40    }
41
42BOOL ICCF_PatchMethod(char *patcheeClassName,
43                      char *patchClassName,
44                      char *patchSuperclassName,
45                      char *selectorString) {
46
47    ICCF_GET_PATCHCLASS(patcheeClass);
48    ICCF_GET_PATCHCLASS(patchClass);
49    ICCF_GET_PATCHCLASS(patchSuperclass);
50
51    SEL selector = sel_getUid(selectorString);
52
53    ICCF_GET_METHOD(patcheeMethod, patcheeClass, selector);
54    ICCF_GET_METHOD(patchMethod, patchClass, selector);
55    ICCF_GET_METHOD(patchSuperMethod, patchSuperclass, selector);
56
57    if (APEPatchCreate(patchSuperMethod->method_imp,
58                       APEPatchCreate(patcheeMethod->method_imp, patchMethod->method_imp)) == NULL) {
59        ICapeprintf("can't patch class %s with [%s %s] super %s", patcheeClassName, patchClassName, selectorString, patchSuperclassName);
60        return NO;
61    }
62    return YES;
63}
64
65CFBundleRef ICCF_bundle;
66
67// With APE 1.3, if we're in the exclude list, APEBundleMainEarlyLoad doesn't get invoked; don't need to use APETools.  But we need to do our own management to avoid loading in background-only applications.
68Boolean ICCF_shouldLoad;
69
70static Boolean ICCF_IsOne(CFTypeRef value) {
71    if (value == NULL)
72        return false;
73    CFTypeID typeID = CFGetTypeID(value);
74    if (typeID == CFBooleanGetTypeID())
75        return CFBooleanGetValue((CFBooleanRef)value);
76    else if (typeID == CFNumberGetTypeID()) {
77        static const int one = 1;
78        static CFNumberRef oneRef = NULL;
79        if (oneRef == NULL) oneRef = CFNumberCreate(NULL, kCFNumberIntType, &one);
80        return CFNumberCompare((CFNumberRef)value, oneRef, NULL) == kCFCompareEqualTo;
81    } else if (typeID == CFStringGetTypeID()) {
82        return CFStringCompare((CFStringRef)value, CFSTR("1"), 0) == kCFCompareEqualTo;
83    }
84    return false;
85}
86
87void APEBundleMainEarlyLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
88{
89    ICCF_MigratePrefs();
90
91    UInt32 icVersion = CFBundleGetVersionNumber(inBundle);
92    ICapeprintf("ICeCoffEE APE: bundle version is %ld (0x%x)\n", icVersion, icVersion);
93    CFNumberRef icVersionRef = CFNumberCreate(NULL, kCFNumberLongType, &icVersion);
94    CFPreferencesSetAppValue(kICLastLoadedVersion, icVersionRef, kICBundleIdentifier);
95    SAFE_RELEASE(icVersionRef);
96
97    ICCF_shouldLoad = false;
98   
99    CFBundleRef appBundle = CFBundleGetMainBundle();
100    if (appBundle == NULL) {
101        apeprintf("ICeCoffEE APE: Can't get CFBundle for current process; not loading\n");
102        return;
103    }
104   
105    if (ICCF_IsOne(CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("LSUIElement"))) ||
106        ICCF_IsOne(CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("NSUIElement"))) ||
107        ICCF_IsOne(CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("LSBackgroundOnly"))) ||
108        ICCF_IsOne(CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("NSBackgroundOnly")))) {
109        ICapeprintf("ICeCoffEE APE: not loading as this application is background-only\n");
110        return;
111    }
112    ICCF_shouldLoad = true;
113}
114
115static Boolean ICCF_CFBundleIDMatches(CFStringRef bundleID, CFStringRef test) {
116    return CFStringCompare(bundleID, test, kCFCompareCaseInsensitive) == kCFCompareEqualTo;
117}
118
119void APEBundleMainLateLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
120{
121    if (!ICCF_shouldLoad) return;
122
123    ICCF_bundle = inBundle;
124    CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
125    BOOL shouldLoadInNSTextView = YES;
126
127    // XXX handle patching error return from ICCF_PatchMethod
128    if (bundleID != NULL) {
129        if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.xcode"))) {
130            ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
131            // XXX fails in Leopard
132            ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for Xcode\n");
133            shouldLoadInNSTextView = NO;
134        } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.terminal"))) {
135            if (ICCF_PatchMethod("TTView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:")) {
136                ICCF_PatchMethod("TTView", "ICeCoffEETTView", "ICeCoffEETTViewSuper", "mouseDown:") &&
137                ICCF_PatchMethod("TTView", "ICeCoffEETTView", "ICeCoffEETTViewSuper", "mouseUp:") &&
138                ICCF_PatchMethod("TTView", "ICeCoffEETTView", "ICeCoffEETTViewSuper", "draggingEntered:");
139                ICapeprintf("ICeCoffEE APE: loaded in TTView for Terminal\n");
140            } else {
141                ICCF_PatchMethod("TermSubview", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:") &&
142                ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICeCoffEETermSubviewSuper", "selectedRange") &&
143                ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICeCoffEETermSubviewSuper", "attributedSubstringFromRange:") &&
144                ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICeCoffEETermSubviewSuper", "mouseDown:") &&
145                ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICeCoffEETermSubviewSuper", "mouseUp:") &&
146                ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICeCoffEETermSubviewSuper", "draggingEntered:") &&
147                ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICeCoffEETermSubviewSuper", "_optionClickEvent:::");
148                ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal\n");
149            }
150        } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("org.mozilla.camino"))) {
151            ICCF_PatchMethod("ChildView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
152            ICapeprintf("ICeCoffEE APE: loaded in ChildView for Camino\n");
153        }
154    }
155
156    ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
157        ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
158        ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:");
159        ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for WebKit/Safari\n");
160   
161    if (shouldLoadInNSTextView) {
162        ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
163        ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:");
164        ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
165    }
166
167    ICCF_ReloadPrefs();
168       
169    return;
170}
171
172// We define APEBundleMessage so we can receive messages from our preference pane.  We actually only know the Refresh message that instructs our APE module to reload the preferences.  Why do we reload preferences here and not do it in our patch? Well, although you can poll CFPreferences in every patch call, that'll result in some performance loss - we better cache the settings in a static variable and refresh them from prefs only when needed.
173OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
174{
175    ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
176   
177    if (CFStringCompare(message, kICPreferencesChanged, 0) == kCFCompareEqualTo)               
178    {   // request to reload prefs from our preference pane
179        ICCF_ReloadPrefs();
180    }
181   
182    return noErr;
183}
184
185Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
186    Boolean keyExists;
187    Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
188    if (keyExists) return value;
189    CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
190    return defaultValue;
191}
192
193CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
194    Boolean keyExists;
195    CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
196    if (keyExists) return value;
197    CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
198    CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
199    CFRelease(defaultValueNumber);
200    return defaultValue;
201}
202
203void ICCF_GetCFTypePref(CFStringRef prefKey, CFTypeRef *val, CFTypeID type) {
204    if (*val != NULL) {
205        CFRelease(*val);
206        *val = NULL;
207    }
208   
209    *val = CFPreferencesCopyAppValue(prefKey, kICBundleIdentifier);
210
211    if (*val == NULL) return;
212
213    if (CFGetTypeID(*val) != type) {
214        CFRelease(*val);
215        *val = NULL;
216        return;
217    }
218}
219
220// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
221static void ICCF_MigratePrefs() {
222   
223    CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
224
225    if (prefExcludedApps == NULL) return;
226
227    if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
228        CFRelease(prefExcludedApps);
229        return;
230    }
231   
232    CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
233    CFRelease(prefExcludedApps); prefExcludedApps = NULL;
234
235    ICapeprintf("Excluded apps: %@\n", excludedApps);
236
237    CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
238    CFIndex excludedAppIndex;
239
240    CFDictionaryRef excludedAppSpecifiers = NULL;
241    CFStringRef excludedAppBundleID = NULL;
242    CFURLRef excludedAppURL = NULL;
243    CFBundleRef excludedAppBundle = NULL;
244
245    BOOL postponeMigrationForApp;
246
247    for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
248        if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
249            CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
250            continue;
251        }
252
253        postponeMigrationForApp = NO;
254        if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
255             && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
256             && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
257            if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
258                excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
259                if (excludedAppBundle != NULL) {
260                    APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle, CFSTR("Migrated from ICeCoffEE 1.0-1.2"), NO);
261                } else postponeMigrationForApp = YES; // can't create bundle
262            } else postponeMigrationForApp = YES; // can't find app
263        }
264        // don't release excludedAppSpecifiers, used Get
265        // don't release excludedAppBundleID, used Get
266        if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
267        if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
268        if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
269    }
270
271    ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
272    CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
273    CFRelease(excludedApps);
274
275    CFPreferencesAppSynchronize(kICBundleIdentifier);
276}
277
278static void ICCF_ReloadPrefs() {
279    CFPreferencesAppSynchronize(kICBundleIdentifier);
280
281    ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
282    ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
283    ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
284    ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
285    ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
286    ICCF_GetCFTypePref(kICServiceOptions, (CFTypeRef *)&ICCF_prefs.serviceOptions, CFDictionaryGetTypeID());
287    ICCF_prefs.terminalRequireOptionForSelfDrag = ICCF_GetBooleanPref(kICTerminalRequireOptionForSelfDrag, NO);
288    ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
289    ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
290
291    CFPreferencesAppSynchronize(kICBundleIdentifier);
292
293    ICCF_AddRemoveServicesMenu();
294}
Note: See TracBrowser for help on using the repository browser.