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

Last change on this file since 295 was 256, checked in by Nicholas Riley, 18 years ago

Removed TextEdit support; merged new Unsanity Installer (3.6.1) and Application Enhancer (2.0.1).

File size: 12.9 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 "SmartCrashReportsAPI.h"
19#import "ICeCoffEE.h"
20#import "CPS.h"
21
22//¥¥¥ Our settings
23
24//¥¥¥ Function prototypes
25static void ICCF_ReloadPrefs(); // reloads our preferences
26static void ICCF_MigratePrefs(); // migrates prefs from 1.0Ð1.2
27
28//¥¥¥ Enter sandman, the code begins --
29
30#define ICCF_GET_PATCHCLASS(patchclass) \
31 struct objc_class *patchclass = objc_getClass(patchclass ## Name); \
32 if (patchclass == NULL) { \
33 ICapeprintf("can't get %s\n", patchclass ## Name); \
34 return NO; \
35 }
36
37#define ICCF_GET_METHOD(name, patchclass, sel) \
38 Method name = class_getInstanceMethod(patchclass, sel); \
39 if (name == NULL) { \
40 ICapeprintf("can't get %s\n", patchclass ## Name); \
41 return NO; \
42 }
43
44BOOL ICCF_PatchMethod(char *patcheeClassName,
45 char *patchClassName,
46 char *patchSuperclassName,
47 char *selectorString) {
48
49 ICCF_GET_PATCHCLASS(patcheeClass);
50 ICCF_GET_PATCHCLASS(patchClass);
51 ICCF_GET_PATCHCLASS(patchSuperclass);
52
53 SEL selector = sel_getUid(selectorString);
54
55 ICCF_GET_METHOD(patcheeMethod, patcheeClass, selector);
56 ICCF_GET_METHOD(patchMethod, patchClass, selector);
57 ICCF_GET_METHOD(patchSuperMethod, patchSuperclass, selector);
58
59 if (APEPatchCreate(patchSuperMethod->method_imp,
60 APEPatchCreate(patcheeMethod->method_imp, patchMethod->method_imp)) == NULL) {
61 ICapeprintf("can't patch class %s with [%s %s] super %s", patcheeClassName, patchClassName, selectorString, patchSuperclassName);
62 return NO;
63 }
64 return YES;
65}
66
67CFBundleRef ICCF_bundle;
68
69// 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 non-GUI applications.
70Boolean ICCF_shouldLoad;
71
72void APEBundleMainEarlyLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
73{
74 OSStatus err;
75
76 err = UnsanitySCR_RegisterMatchSpecifier(kICBundleIdentifier, CFSTR("Nicholas Riley"),
77 NULL, CFSTR("SCR-5E5C696784"), NULL);
78 if (err != noErr) {
79 apeprintf("ICeCoffEE APE: Can't register with Smart Crash Reports (error %ld)\n", err);
80 }
81
82 ICCF_MigratePrefs();
83
84 UInt32 icVersion = CFBundleGetVersionNumber(inBundle);
85 ICapeprintf("ICeCoffEE APE: bundle version is %ld (0x%x)\n", icVersion, icVersion);
86 CFNumberRef icVersionRef = CFNumberCreate(NULL, kCFNumberLongType, &icVersion);
87 CFPreferencesSetAppValue(kICLastLoadedVersion, icVersionRef, kICBundleIdentifier);
88 SAFE_RELEASE(icVersionRef);
89
90 ICCF_shouldLoad = false;
91
92 CPSProcessSerNum psn;
93 err = CPSGetCurrentProcess(&psn);
94 if (err != noErr) {
95#if ICCF_DEBUG
96 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); still loading because of ICCF_DEBUG\n", err);
97#else
98 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); not loading in this application\n", err);
99 return;
100#endif
101 }
102
103 CPSProcessInfoRec info;
104 err = CPSGetProcessInfo(&psn, &info, NULL, 0, NULL, NULL, 0);
105 if (err != noErr) {
106#if ICCF_DEBUG
107 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); still loading because of ICCF_DEBUG\n", err);
108#else
109 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); not loading in this application\n", err);
110 return;
111#endif
112 } else {
113 ICapeprintf("ICeCoffEE APE: got process attributes = 0x%lx\n", info.Attributes);
114 if (info.Attributes & (kCPSBGOnlyAttr | kCPSUIElementAttr | kCPSFullScreenAttr)) {
115 ICapeprintf("ICeCoffEE APE: not loading as this application is background-only\n");
116 return;
117 }
118 }
119 ICCF_shouldLoad = true;
120}
121
122Boolean ICCF_CFBundleIDMatches(CFStringRef bundleID, CFStringRef test) {
123 return CFStringCompare(bundleID, test, kCFCompareCaseInsensitive) == kCFCompareEqualTo;
124}
125
126void APEBundleMainLateLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
127{
128 if (!ICCF_shouldLoad) return;
129
130 ICCF_bundle = CFBundleGetBundleWithIdentifier(kICBundleIdentifier);
131 CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
132 BOOL shouldLoadInNSTextView = YES;
133
134 // XXX handle patching error return from ICCF_PatchMethod
135 if (bundleID != NULL) {
136 if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.xcode"))) {
137 ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
138 ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for Xcode\n");
139 shouldLoadInNSTextView = NO;
140 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.terminal"))) {
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 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("org.mozilla.camino"))) {
150 ICCF_PatchMethod("ChildView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
151 ICapeprintf("ICeCoffEE APE: loaded in ChildView for Camino\n");
152 }
153 }
154
155 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
156 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
157 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:");
158 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for WebKit/Safari\n");
159
160 if (shouldLoadInNSTextView) {
161 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
162 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:");
163 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
164 }
165
166 ICCF_ReloadPrefs();
167
168 return;
169}
170
171// 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.
172OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
173{
174 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
175
176 if (CFStringCompare(message, kICPreferencesChanged, 0) == kCFCompareEqualTo)
177 { // request to reload prefs from our preference pane
178 ICCF_ReloadPrefs();
179 }
180
181 return noErr;
182}
183
184Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
185 Boolean keyExists;
186 Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
187 if (keyExists) return value;
188 CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
189 return defaultValue;
190}
191
192CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
193 Boolean keyExists;
194 CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
195 if (keyExists) return value;
196 CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
197 CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
198 CFRelease(defaultValueNumber);
199 return defaultValue;
200}
201
202void ICCF_GetCFTypePref(CFStringRef prefKey, CFTypeRef *val, CFTypeID type) {
203 if (*val != NULL) {
204 CFRelease(*val);
205 *val = NULL;
206 }
207
208 *val = CFPreferencesCopyAppValue(prefKey, kICBundleIdentifier);
209
210 if (*val == NULL) return;
211
212 if (CFGetTypeID(*val) != type) {
213 CFRelease(*val);
214 *val = NULL;
215 return;
216 }
217}
218
219// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
220static void ICCF_MigratePrefs() {
221
222 CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
223
224 if (prefExcludedApps == NULL) return;
225
226 if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
227 CFRelease(prefExcludedApps);
228 return;
229 }
230
231 CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
232 CFRelease(prefExcludedApps); prefExcludedApps = NULL;
233
234 ICapeprintf("Excluded apps: %@\n", excludedApps);
235
236 CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
237 CFIndex excludedAppIndex;
238
239 CFDictionaryRef excludedAppSpecifiers = NULL;
240 CFStringRef excludedAppBundleID = NULL;
241 CFURLRef excludedAppURL = NULL;
242 CFBundleRef excludedAppBundle = NULL;
243
244 BOOL postponeMigrationForApp;
245
246 for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
247 if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
248 CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
249 continue;
250 }
251
252 postponeMigrationForApp = NO;
253 if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
254 && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
255 && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
256 if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
257 excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
258 if (excludedAppBundle != NULL) {
259 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle, CFSTR("Migrated from ICeCoffEE 1.0-1.2"), NO);
260 } else postponeMigrationForApp = YES; // can't create bundle
261 } else postponeMigrationForApp = YES; // can't find app
262 }
263 // don't release excludedAppSpecifiers, used Get
264 // don't release excludedAppBundleID, used Get
265 if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
266 if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
267 if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
268 }
269
270 ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
271 CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
272 CFRelease(excludedApps);
273
274 CFPreferencesAppSynchronize(kICBundleIdentifier);
275}
276
277static void ICCF_ReloadPrefs() {
278 CFPreferencesAppSynchronize(kICBundleIdentifier);
279
280 ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
281 ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
282 ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
283 ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
284 ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
285 ICCF_GetCFTypePref(kICServiceOptions, (CFTypeRef *)&ICCF_prefs.serviceOptions, CFDictionaryGetTypeID());
286 ICCF_prefs.terminalRequireOptionForSelfDrag = ICCF_GetBooleanPref(kICTerminalRequireOptionForSelfDrag, NO);
287 ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
288 ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
289
290 CFPreferencesAppSynchronize(kICBundleIdentifier);
291
292 ICCF_AddRemoveServicesMenu();
293}
Note: See TracBrowser for help on using the repository browser.