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

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