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

Last change on this file since 140 was 139, checked in by Nicholas Riley, 21 years ago
File size: 12.0 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", "draggingExited:") &&
133 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "_optionClickEvent:::") ,
134 ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal!\n");
135 } else if (CFStringCompare(bundleID, CFSTR("com.apple.safari"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
136#if ICCF_WEBKIT
137 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
138 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
139 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:") ,
140 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for Safari!\n");
141#endif
142 }
143 }
144
145 if (shouldLoadInNSTextView) {
146 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
147 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:") ,
148 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
149 }
150
151 gTEClick = APEPatchCreate(&TEClick, &ICCF_TEClick);
152 if (gTEClick != NULL) {
153 ICapeprintf("ICeCoffEE APE: patched TEClick\n");
154 }
155
156 ICCF_ReloadPrefs();
157
158 return;
159}
160
161// 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.
162OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
163{
164 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
165
166 if (CFStringCompare(message, kICPreferencesChanged, NULL) == kCFCompareEqualTo)
167 { // request to reload prefs from our preference pane
168 ICCF_ReloadPrefs();
169 }
170
171 return noErr;
172}
173
174Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
175 Boolean keyExists;
176 Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
177 if (keyExists) return value;
178 CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
179 return defaultValue;
180}
181
182CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
183 Boolean keyExists;
184 CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
185 if (keyExists) return value;
186 CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
187 CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
188 CFRelease(defaultValueNumber);
189 return defaultValue;
190}
191
192// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
193static void ICCF_MigratePrefs() {
194
195 CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
196
197 if (prefExcludedApps == NULL) return;
198
199 if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
200 CFRelease(prefExcludedApps);
201 return;
202 }
203
204 CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
205 CFRelease(prefExcludedApps); prefExcludedApps = NULL;
206
207 ICapeprintf("Excluded apps: %@\n", excludedApps);
208
209 CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
210 CFIndex excludedAppIndex;
211
212 CFDictionaryRef excludedAppSpecifiers = NULL;
213 CFStringRef excludedAppBundleID = NULL;
214 CFURLRef excludedAppURL = NULL;
215 CFBundleRef excludedAppBundle = NULL;
216
217 BOOL postponeMigrationForApp;
218
219 for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
220 if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
221 CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
222 continue;
223 }
224
225 postponeMigrationForApp = NO;
226 if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
227 && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
228 && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
229 if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
230 excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
231 if (excludedAppBundle != NULL) {
232 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle);
233 } else postponeMigrationForApp = YES; // can't create bundle
234 } else postponeMigrationForApp = YES; // can't find app
235 }
236 // don't release excludedAppSpecifiers, used Get
237 // don't release excludedAppBundleID, used Get
238 if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
239 if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
240 if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
241 }
242
243 ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
244 CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
245 CFRelease(excludedApps);
246
247 CFPreferencesAppSynchronize(kICBundleIdentifier);
248}
249
250static void ICCF_ReloadPrefs() {
251 CFPreferencesAppSynchronize(kICBundleIdentifier);
252
253 ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
254 ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
255 ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
256 ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
257 ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
258 ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
259 ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
260
261 CFPreferencesAppSynchronize(kICBundleIdentifier);
262
263 ICCF_AddRemoveServicesMenu();
264}
Note: See TracBrowser for help on using the repository browser.