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

Last change on this file since 89 was 89, checked in by Nicholas Riley, 21 years ago

ICeCoffEE 1.3.1

File size: 11.4 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 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); not loading in this application\n", err);
92 return;
93 }
94
95 CPSProcessInfoRec info;
96 err = CPSGetProcessInfo(&psn, &info, NULL, 0, NULL, NULL, 0);
97 if (err != noErr) {
98 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); not loading in this application\n", err);
99 return;
100 }
101 ICapeprintf("ICeCoffEE APE: got process attributes = 0x%lx\n", info.Attributes);
102 if (info.Attributes & (kCPSBGOnlyAttr | kCPSUIElementAttr | kCPSFullScreenAttr)) {
103 ICapeprintf("ICeCoffEE APE: not loading as this application is background-only\n");
104 return;
105 }
106
107 ICCF_bundle = CFBundleGetBundleWithIdentifier(kICBundleIdentifier);
108 CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
109 BOOL shouldLoadInNSTextView = YES;
110
111 // XXX handle patching error return from ICCF_PatchMethod
112 if (bundleID != NULL) {
113 if (CFStringCompare(bundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
114 ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
115 ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for PB!\n");
116 shouldLoadInNSTextView = NO;
117 } else if (CFStringCompare(bundleID, CFSTR("com.apple.terminal"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
118 ICCF_PatchMethod("TermSubview", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:") &&
119 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "selectedRange") &&
120 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "attributedSubstringFromRange:") &&
121 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseDown:") &&
122 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseUp:") &&
123 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "_optionClickEvent:::") ,
124 ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal!\n");
125 } else if (CFStringCompare(bundleID, CFSTR("com.apple.safari"), kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
126 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
127 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
128 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:") ,
129 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for Safari!\n");
130 }
131 }
132
133 if (shouldLoadInNSTextView) {
134 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
135 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:") ,
136 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
137 }
138
139 gTEClick = APEPatchCreate(&TEClick, &ICCF_TEClick);
140 if (gTEClick != NULL) {
141 ICapeprintf("ICeCoffEE APE: patched TEClick\n");
142 }
143
144 ICCF_ReloadPrefs();
145
146 return;
147}
148
149// 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.
150OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
151{
152 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
153
154 if (CFStringCompare(message, kICPreferencesChanged, NULL) == kCFCompareEqualTo)
155 { // request to reload prefs from our preference pane
156 ICCF_ReloadPrefs();
157 }
158
159 return noErr;
160}
161
162Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
163 Boolean keyExists;
164 Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
165 if (keyExists) return value;
166 CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
167 return defaultValue;
168}
169
170CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
171 Boolean keyExists;
172 CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
173 if (keyExists) return value;
174 CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
175 CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
176 CFRelease(defaultValueNumber);
177 return defaultValue;
178}
179
180// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
181static void ICCF_MigratePrefs() {
182
183 CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
184
185 if (prefExcludedApps == NULL) return;
186
187 if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
188 CFRelease(prefExcludedApps);
189 return;
190 }
191
192 CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
193 CFRelease(prefExcludedApps); prefExcludedApps = NULL;
194
195 ICapeprintf("Excluded apps: %@\n", excludedApps);
196
197 CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
198 CFIndex excludedAppIndex;
199
200 CFDictionaryRef excludedAppSpecifiers = NULL;
201 CFStringRef excludedAppBundleID = NULL;
202 CFURLRef excludedAppURL = NULL;
203 CFBundleRef excludedAppBundle = NULL;
204
205 BOOL postponeMigrationForApp;
206
207 for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
208 if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
209 CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
210 continue;
211 }
212
213 postponeMigrationForApp = NO;
214 if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
215 && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
216 && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
217 if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
218 excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
219 if (excludedAppBundle != NULL) {
220 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle);
221 } else postponeMigrationForApp = YES; // can't create bundle
222 } else postponeMigrationForApp = YES; // can't find app
223 }
224 // don't release excludedAppSpecifiers, used Get
225 // don't release excludedAppBundleID, used Get
226 if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
227 if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
228 if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
229 }
230
231 ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
232 CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
233 CFRelease(excludedApps);
234
235 CFPreferencesAppSynchronize(kICBundleIdentifier);
236}
237
238static void ICCF_ReloadPrefs() {
239 CFPreferencesAppSynchronize(kICBundleIdentifier);
240
241 ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
242 ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
243 ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
244 ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
245 ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
246 ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
247 ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
248
249 CFPreferencesAppSynchronize(kICBundleIdentifier);
250
251 ICCF_AddRemoveServicesMenu();
252}
Note: See TracBrowser for help on using the repository browser.