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

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