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

Last change on this file since 106 was 106, checked in by Nicholas Riley, 19 years ago

ICeCoffEE 1.3.2b1

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