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

Last change on this file since 181 was 181, checked in by Nicholas Riley, 17 years ago

ICeCoffEEAction.c: Replace undocumented _LSCopyApplicationURLsForItemURL
with LSCopyApplicationURLsForURL, which was introduced in 10.3.

ICFindFilesToRemove/UICookieMonster.m: Fix some minor bugs revealed by
new compiler warnings in Apple's GCC 4.0.

ICeCoffEETextEdit.c: Pass an unsigned long instead of a SInt32 to
Delay(), as intended (another GCC 4.0 nit-pick).

English.lproj/InfoPlist.strings: Update version number.

ICeCoffEEShared.h: Enable debugging.

ICeCoffEE APE.xcode: Changes for Xcode 2.0.

ICeCoffEE.m: Casts to satisfy GCC 4.0.

APEMain.m: Fix CFStringCompare third argument: options, not a pointer.
Yet another dumb coding mistake pointed out courtesy of GCC 4.0.

File size: 12.8 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#import "ICeCoffEETextEdit.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 appllications.
70Boolean ICCF_shouldLoad;
71
72void APEBundleMainEarlyLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
73{
74 ICCF_MigratePrefs();
75
76 UInt32 icVersion = CFBundleGetVersionNumber(inBundle);
77 ICapeprintf("ICeCoffEE APE: bundle version is %ld (0x%x)\n", icVersion, icVersion);
78 CFNumberRef icVersionRef = CFNumberCreate(NULL, kCFNumberLongType, &icVersion);
79 CFPreferencesSetAppValue(kICLastLoadedVersion, icVersionRef, kICBundleIdentifier);
80 SAFE_RELEASE(icVersionRef);
81
82 ICCF_shouldLoad = false;
83
84 CPSProcessSerNum psn;
85 OSStatus err = CPSGetCurrentProcess(&psn);
86 if (err != noErr) {
87#if ICCF_DEBUG
88 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); still loading because of ICCF_DEBUG\n", err);
89#else
90 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); not loading in this application\n", err);
91 return;
92#endif
93 }
94
95 CPSProcessInfoRec info;
96 err = CPSGetProcessInfo(&psn, &info, NULL, 0, NULL, NULL, 0);
97 if (err != noErr) {
98#if ICCF_DEBUG
99 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); still loading because of ICCF_DEBUG\n", err);
100#else
101 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); not loading in this application\n", err);
102 return;
103#endif
104 } else {
105 ICapeprintf("ICeCoffEE APE: got process attributes = 0x%lx\n", info.Attributes);
106 if (info.Attributes & (kCPSBGOnlyAttr | kCPSUIElementAttr | kCPSFullScreenAttr)) {
107 ICapeprintf("ICeCoffEE APE: not loading as this application is background-only\n");
108 return;
109 }
110 }
111 ICCF_shouldLoad = true;
112}
113
114Boolean ICCF_CFBundleIDMatches(CFStringRef bundleID, CFStringRef test) {
115 return CFStringCompare(bundleID, test, kCFCompareCaseInsensitive) == kCFCompareEqualTo;
116}
117
118void APEBundleMainLateLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
119{
120 if (!ICCF_shouldLoad) return;
121
122 ICCF_bundle = CFBundleGetBundleWithIdentifier(kICBundleIdentifier);
123 CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
124 BOOL shouldLoadInNSTextView = YES;
125
126 // XXX handle patching error return from ICCF_PatchMethod
127 if (bundleID != NULL) {
128 if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.projectbuilder")) ||
129 ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.xcode"))) {
130 ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
131 ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for PB!\n");
132 shouldLoadInNSTextView = NO;
133 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.terminal"))) {
134 ICCF_PatchMethod("TermSubview", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:") &&
135 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "selectedRange") &&
136 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "attributedSubstringFromRange:") &&
137 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseDown:") &&
138 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseUp:") &&
139 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "draggingEntered:") &&
140 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "_optionClickEvent:::") ,
141 ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal!\n");
142 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("org.mozilla.navigator"))) {
143 ICCF_PatchMethod("ChildView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
144 ICapeprintf("ICeCoffEE APE: loaded in ChildView for Camino\n");
145 }
146 }
147
148 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
149 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
150 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:"),
151 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for WebKit/Safari\n");
152
153 if (shouldLoadInNSTextView) {
154 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
155 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:") ,
156 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
157 }
158
159 gTEClick = APEPatchCreate(&TEClick, &ICCF_TEClick);
160 if (gTEClick != NULL) {
161 ICapeprintf("ICeCoffEE APE: patched TEClick\n");
162 }
163
164 ICCF_ReloadPrefs();
165
166 return;
167}
168
169// 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.
170OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
171{
172 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
173
174 if (CFStringCompare(message, kICPreferencesChanged, 0) == kCFCompareEqualTo)
175 { // request to reload prefs from our preference pane
176 ICCF_ReloadPrefs();
177 }
178
179 return noErr;
180}
181
182Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
183 Boolean keyExists;
184 Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
185 if (keyExists) return value;
186 CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
187 return defaultValue;
188}
189
190CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
191 Boolean keyExists;
192 CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
193 if (keyExists) return value;
194 CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
195 CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
196 CFRelease(defaultValueNumber);
197 return defaultValue;
198}
199
200void ICCF_GetCFTypePref(CFStringRef prefKey, CFTypeRef *val, CFTypeID type) {
201 if (*val != NULL) {
202 CFRelease(*val);
203 *val = NULL;
204 }
205
206 *val = CFPreferencesCopyAppValue(prefKey, kICBundleIdentifier);
207
208 if (*val == NULL) return;
209
210 if (CFGetTypeID(*val) != type) {
211 CFRelease(*val);
212 *val = NULL;
213 return;
214 }
215}
216
217// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
218static void ICCF_MigratePrefs() {
219
220 CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
221
222 if (prefExcludedApps == NULL) return;
223
224 if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
225 CFRelease(prefExcludedApps);
226 return;
227 }
228
229 CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
230 CFRelease(prefExcludedApps); prefExcludedApps = NULL;
231
232 ICapeprintf("Excluded apps: %@\n", excludedApps);
233
234 CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
235 CFIndex excludedAppIndex;
236
237 CFDictionaryRef excludedAppSpecifiers = NULL;
238 CFStringRef excludedAppBundleID = NULL;
239 CFURLRef excludedAppURL = NULL;
240 CFBundleRef excludedAppBundle = NULL;
241
242 BOOL postponeMigrationForApp;
243
244 for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
245 if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
246 CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
247 continue;
248 }
249
250 postponeMigrationForApp = NO;
251 if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
252 && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
253 && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
254 if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
255 excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
256 if (excludedAppBundle != NULL) {
257 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle, CFSTR("Migrated from ICeCoffEE 1.0-1.2"), NO);
258 } else postponeMigrationForApp = YES; // can't create bundle
259 } else postponeMigrationForApp = YES; // can't find app
260 }
261 // don't release excludedAppSpecifiers, used Get
262 // don't release excludedAppBundleID, used Get
263 if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
264 if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
265 if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
266 }
267
268 ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
269 CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
270 CFRelease(excludedApps);
271
272 CFPreferencesAppSynchronize(kICBundleIdentifier);
273}
274
275static void ICCF_ReloadPrefs() {
276 CFPreferencesAppSynchronize(kICBundleIdentifier);
277
278 ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
279 ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
280 ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
281 ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
282 ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
283 ICCF_GetCFTypePref(kICServiceOptions, (CFTypeRef *)&ICCF_prefs.serviceOptions, CFDictionaryGetTypeID());
284 ICCF_prefs.terminalRequireOptionForSelfDrag = ICCF_GetBooleanPref(kICTerminalRequireOptionForSelfDrag, NO);
285 ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
286 ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
287
288 CFPreferencesAppSynchronize(kICBundleIdentifier);
289
290 ICCF_AddRemoveServicesMenu();
291}
Note: See TracBrowser for help on using the repository browser.