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

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

ICeCoffEE 1.3b2 plus some changes for 1.3

File size: 10.3 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
21//¥¥¥ Our settings
22
23//¥¥¥ Function prototypes
24static void ICCF_ReloadPrefs();                         // reloads our preferences
25static void ICCF_MigratePrefs();                        // migrates prefs from 1.0Ð1.2
26
27//¥¥¥ Enter sandman, the code begins --
28
29#define ICCF_GET_PATCHCLASS(patchclass) \
30    struct objc_class *patchclass = objc_getClass(patchclass ## Name); \
31    if (patchclass == NULL) { \
32        ICapeprintf("can't get %s\n", patchclass ## Name); \
33        return NO; \
34    }
35
36#define ICCF_GET_METHOD(name, patchclass, sel) \
37    Method name = class_getInstanceMethod(patchclass, sel); \
38    if (name == NULL) { \
39        ICapeprintf("can't get %s\n", patchclass ## Name); \
40        return NO; \
41    }
42
43BOOL ICCF_PatchMethod(char *patcheeClassName,
44                      char *patchClassName,
45                      char *patchSuperclassName,
46                      char *selectorString) {
47
48    ICCF_GET_PATCHCLASS(patcheeClass);
49    ICCF_GET_PATCHCLASS(patchClass);
50    ICCF_GET_PATCHCLASS(patchSuperclass);
51
52    SEL selector = sel_getUid(selectorString);
53
54    ICCF_GET_METHOD(patcheeMethod, patcheeClass, selector);
55    ICCF_GET_METHOD(patchMethod, patchClass, selector);
56    ICCF_GET_METHOD(patchSuperMethod, patchSuperclass, selector);
57
58    if (APEPatchCreate(patchSuperMethod->method_imp,
59                       APEPatchCreate(patcheeMethod->method_imp, patchMethod->method_imp)) == NULL) {
60        ICapeprintf("can't patch class %s with [%s %s] super %s", patcheeClassName, patchClassName, selectorString, patchSuperclassName);
61        return NO;
62    }
63    return YES;
64}
65
66CFBundleRef ICCF_bundle;
67
68void APEBundleMain(CFBundleRef inBundle)
69{
70    ICCF_MigratePrefs();
71
72    // first check if this application is in the exclude list;
73    // if it is, simply return and do not apply any patches.
74    // APETools will help us with that; APE Manager will take care of
75    // exclude list management for us.
76    if (APEToolsIsInExcludeList(kICBundleIdentifier, NULL))
77    {
78        ICapeprintf("ICeCoffEE APE: not loading as this application is excluded.\n");
79        return;
80    }
81
82    ICCF_bundle = CFBundleGetBundleWithIdentifier(kICBundleIdentifier);
83    CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
84    BOOL shouldLoadInNSTextView = YES;
85
86    // XXX handle patching error return from ICCF_PatchMethod
87    if (bundleID != NULL) {
88        if (CFStringCompare(bundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
89            ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
90            ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for PB!\n");
91            shouldLoadInNSTextView = NO;
92        } else if (CFStringCompare(bundleID, CFSTR("com.apple.terminal"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
93            ICCF_PatchMethod("TermSubview", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:") &&
94            ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "selectedRange") &&
95            ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "attributedSubstringFromRange:") &&
96            ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseDown:") &&
97            ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseUp:") &&
98            ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "_optionClickEvent:::") ,
99            ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal!\n");
100        } else if (CFStringCompare(bundleID, CFSTR("com.apple.safari"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
101            ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
102            ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
103            ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:") ,
104            ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for Safari!\n");
105        }
106    }
107
108    if (shouldLoadInNSTextView) {
109        ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
110        ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:") ,
111        ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
112    }
113
114    gTEClick = APEPatchCreate(&TEClick, &ICCF_TEClick);
115    if (gTEClick != NULL) {
116        ICapeprintf("ICeCoffEE APE: patched TEClick\n");
117    }
118
119    ICCF_ReloadPrefs();
120       
121    return;
122}
123
124// 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.
125OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
126{
127    ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
128   
129    if (CFStringCompare(message, kICPreferencesChanged, NULL) == kCFCompareEqualTo)             
130    {   // request to reload prefs from our preference pane
131        ICCF_ReloadPrefs();
132    }
133   
134    return noErr;
135}
136
137Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
138    Boolean keyExists;
139    Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
140    if (keyExists) return value;
141    CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
142    return defaultValue;
143}
144
145CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
146    Boolean keyExists;
147    CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
148    if (keyExists) return value;
149    CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
150    CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
151    CFRelease(defaultValueNumber);
152    return defaultValue;
153}
154
155// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
156static void ICCF_MigratePrefs() {
157   
158    CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
159
160    if (prefExcludedApps == NULL) return;
161
162    if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
163        CFRelease(prefExcludedApps);
164        return;
165    }
166   
167    CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
168    CFRelease(prefExcludedApps); prefExcludedApps = NULL;
169
170    ICapeprintf("Excluded apps: %@\n", excludedApps);
171
172    CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
173    CFIndex excludedAppIndex;
174
175    CFDictionaryRef excludedAppSpecifiers = NULL;
176    CFStringRef excludedAppBundleID = NULL;
177    CFURLRef excludedAppURL = NULL;
178    CFBundleRef excludedAppBundle = NULL;
179
180    BOOL postponeMigrationForApp;
181
182    for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
183        if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
184            CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
185            continue;
186        }
187
188        postponeMigrationForApp = NO;
189        if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
190             && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
191             && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
192            if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
193                excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
194                if (excludedAppBundle != NULL) {
195                    APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle);
196                } else postponeMigrationForApp = YES; // can't create bundle
197            } else postponeMigrationForApp = YES; // can't find app
198        }
199        // don't release excludedAppSpecifiers, used Get
200        // don't release excludedAppBundleID, used Get
201        if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
202        if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
203        if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
204    }
205
206    ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
207    CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
208    CFRelease(excludedApps);
209
210    CFPreferencesAppSynchronize(kICBundleIdentifier);
211}
212
213static void ICCF_ReloadPrefs() {
214    CFPreferencesAppSynchronize(kICBundleIdentifier);
215
216    ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
217    ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
218    ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
219    ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
220    ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
221    ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
222    ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
223
224    CFPreferencesAppSynchronize(kICBundleIdentifier);
225
226    ICCF_AddRemoveServicesMenu();
227}
Note: See TracBrowser for help on using the repository browser.