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

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

Remove unnecessary CPS usage and CFBundleGetBundleWithIdentifier.

File size: 13.0 KB
RevLine 
[66]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
[74]24static void ICCF_MigratePrefs(); // migrates prefs from 1.0Ð1.2
[66]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) { \
[74]31 ICapeprintf("can't get %s\n", patchclass ## Name); \
[66]32 return NO; \
33 }
34
35#define ICCF_GET_METHOD(name, patchclass, sel) \
36 Method name = class_getInstanceMethod(patchclass, sel); \
37 if (name == NULL) { \
[74]38 ICapeprintf("can't get %s\n", patchclass ## Name); \
[66]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
[74]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 }
[66]62 return YES;
63}
64
[74]65CFBundleRef ICCF_bundle;
66
[389]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.
[167]68Boolean ICCF_shouldLoad;
69
[389]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
[167]87void APEBundleMainEarlyLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
[66]88{
[74]89 ICCF_MigratePrefs();
90
[89]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
[167]97 ICCF_shouldLoad = false;
[389]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;
[88]103 }
[389]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;
[88]111 }
[167]112 ICCF_shouldLoad = true;
113}
[88]114
[389]115static Boolean ICCF_CFBundleIDMatches(CFStringRef bundleID, CFStringRef test) {
[167]116 return CFStringCompare(bundleID, test, kCFCompareCaseInsensitive) == kCFCompareEqualTo;
117}
118
119void APEBundleMainLateLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
120{
121 if (!ICCF_shouldLoad) return;
122
[389]123 ICCF_bundle = inBundle;
[66]124 CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
[74]125 BOOL shouldLoadInNSTextView = YES;
126
127 // XXX handle patching error return from ICCF_PatchMethod
[66]128 if (bundleID != NULL) {
[183]129 if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.xcode"))) {
[66]130 ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
[388]131 // XXX fails in Leopard
[216]132 ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for Xcode\n");
[74]133 shouldLoadInNSTextView = NO;
[167]134 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.terminal"))) {
[388]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"))) {
[167]151 ICCF_PatchMethod("ChildView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
152 ICapeprintf("ICeCoffEE APE: loaded in ChildView for Camino\n");
[66]153 }
154 }
155
[167]156 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
157 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
[216]158 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:");
[167]159 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for WebKit/Safari\n");
160
[74]161 if (shouldLoadInNSTextView) {
162 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
[216]163 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:");
[74]164 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
[66]165 }
166
[74]167 ICCF_ReloadPrefs();
[66]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{
[74]175 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
[66]176
[181]177 if (CFStringCompare(message, kICPreferencesChanged, 0) == kCFCompareEqualTo)
[66]178 { // request to reload prefs from our preference pane
179 ICCF_ReloadPrefs();
180 }
181
182 return noErr;
183}
184
[74]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}
[66]192
[74]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;
[66]201}
[74]202
[142]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
[74]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) {
[142]260 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle, CFSTR("Migrated from ICeCoffEE 1.0-1.2"), NO);
[74]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);
[142]286 ICCF_GetCFTypePref(kICServiceOptions, (CFTypeRef *)&ICCF_prefs.serviceOptions, CFDictionaryGetTypeID());
287 ICCF_prefs.terminalRequireOptionForSelfDrag = ICCF_GetBooleanPref(kICTerminalRequireOptionForSelfDrag, NO);
[74]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.