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

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

VERSION: Starting with 1.4.3d1.

APE.icns: Generic APE icon (not sure whether we care).

SmartCrashReportsAPI.[ho]: SCR 1.1.

ICeCoffEEWebKit.m: -elementAtPoint: isn't in current
development WebKit; switch to the version in WebHTMLView.

Info-APE Module.plist: Update version to 1.4.3d1.

ICeCoffEE.xcodeproj: Xcode 2 version of project, required fixing
script which copies the APE bundle into the installer. We're still
building for 10.3.9 and later on PowerPC only for this release.

English.lproj/APEInfo.rtfd: Small clarifications, update release notes
and version information.

English.lproj/InfoPlist.strings: Update version to 1.4.3d1.

ICeCoffEEShared.h: Use varargs macros to finally fix the stupid
warnings when ICCF_DEBUG is 0.

ICeCoffEE APE.xcode: Removed.

ICeCoffEETerminal.m: Fixes crash on clicking disabled close/minimize
widgets in Open dialog (invalid super method call) by implementing
overridden methods in the faked superclass.

ape_install: APE 1.5.1.

Info-APEManagerPrefPane.plist: Update version number.

APEMain.m: Implement SCR. Fix a comment typo. Remove some useless
uses of the comma operator so the new warning-less ICapeprintf works.

package-ICeCoffEE.sh: Use new xcodebuild syntax and build layout.
Remove a useless use of the semicolon. Nuke localizations on
development builds.

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