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

Last change on this file since 216 was 216, checked in by Nicholas Riley, 14 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.