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

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

ICeCoffEE 1.4a1

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