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

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

ICeCoffEE 1.3.1d1

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