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

Last change on this file since 378 was 378, checked in by Nicholas Riley, 16 years ago

Update for SCR 1.5, no longer registering explicitly.

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 "CPS.h"
20
21//¥¥¥ Our settings
22
23//¥¥¥ Function prototypes
24static void ICCF_ReloadPrefs(); // reloads our preferences
25static void ICCF_MigratePrefs(); // migrates prefs from 1.0Ð1.2
26
27//¥¥¥ Enter sandman, the code begins --
28
29#define ICCF_GET_PATCHCLASS(patchclass) \
30 struct objc_class *patchclass = objc_getClass(patchclass ## Name); \
31 if (patchclass == NULL) { \
32 ICapeprintf("can't get %s\n", patchclass ## Name); \
33 return NO; \
34 }
35
36#define ICCF_GET_METHOD(name, patchclass, sel) \
37 Method name = class_getInstanceMethod(patchclass, sel); \
38 if (name == NULL) { \
39 ICapeprintf("can't get %s\n", patchclass ## Name); \
40 return NO; \
41 }
42
43BOOL ICCF_PatchMethod(char *patcheeClassName,
44 char *patchClassName,
45 char *patchSuperclassName,
46 char *selectorString) {
47
48 ICCF_GET_PATCHCLASS(patcheeClass);
49 ICCF_GET_PATCHCLASS(patchClass);
50 ICCF_GET_PATCHCLASS(patchSuperclass);
51
52 SEL selector = sel_getUid(selectorString);
53
54 ICCF_GET_METHOD(patcheeMethod, patcheeClass, selector);
55 ICCF_GET_METHOD(patchMethod, patchClass, selector);
56 ICCF_GET_METHOD(patchSuperMethod, patchSuperclass, selector);
57
58 if (APEPatchCreate(patchSuperMethod->method_imp,
59 APEPatchCreate(patcheeMethod->method_imp, patchMethod->method_imp)) == NULL) {
60 ICapeprintf("can't patch class %s with [%s %s] super %s", patcheeClassName, patchClassName, selectorString, patchSuperclassName);
61 return NO;
62 }
63 return YES;
64}
65
66CFBundleRef ICCF_bundle;
67
68// With APE 1.3, if we're in the exclude list, APEBundleMainEarlyLoad doesn't get invoked; don't need to use APETools. But we need to do our own management to avoid loading in non-GUI applications.
69Boolean ICCF_shouldLoad;
70
71void APEBundleMainEarlyLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
72{
73 ICCF_MigratePrefs();
74
75 UInt32 icVersion = CFBundleGetVersionNumber(inBundle);
76 ICapeprintf("ICeCoffEE APE: bundle version is %ld (0x%x)\n", icVersion, icVersion);
77 CFNumberRef icVersionRef = CFNumberCreate(NULL, kCFNumberLongType, &icVersion);
78 CFPreferencesSetAppValue(kICLastLoadedVersion, icVersionRef, kICBundleIdentifier);
79 SAFE_RELEASE(icVersionRef);
80
81 ICCF_shouldLoad = false;
82
83 CPSProcessSerNum psn;
84 OSStatus err = CPSGetCurrentProcess(&psn);
85 if (err != noErr) {
86#if ICCF_DEBUG
87 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); still loading because of ICCF_DEBUG\n", err);
88#else
89 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); not loading in this application\n", err);
90 return;
91#endif
92 }
93
94 CPSProcessInfoRec info;
95 err = CPSGetProcessInfo(&psn, &info, NULL, 0, NULL, NULL, 0);
96 if (err != noErr) {
97#if ICCF_DEBUG
98 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); still loading because of ICCF_DEBUG\n", err);
99#else
100 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); not loading in this application\n", err);
101 return;
102#endif
103 } else {
104 ICapeprintf("ICeCoffEE APE: got process attributes = 0x%lx\n", info.Attributes);
105 if (info.Attributes & (kCPSBGOnlyAttr | kCPSUIElementAttr | kCPSFullScreenAttr)) {
106 ICapeprintf("ICeCoffEE APE: not loading as this application is background-only\n");
107 return;
108 }
109 }
110 ICCF_shouldLoad = true;
111}
112
113Boolean ICCF_CFBundleIDMatches(CFStringRef bundleID, CFStringRef test) {
114 return CFStringCompare(bundleID, test, kCFCompareCaseInsensitive) == kCFCompareEqualTo;
115}
116
117void APEBundleMainLateLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
118{
119 if (!ICCF_shouldLoad) return;
120
121 ICCF_bundle = CFBundleGetBundleWithIdentifier(kICBundleIdentifier);
122 CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
123 BOOL shouldLoadInNSTextView = YES;
124
125 // XXX handle patching error return from ICCF_PatchMethod
126 if (bundleID != NULL) {
127 if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.xcode"))) {
128 ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
129 ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for Xcode\n");
130 shouldLoadInNSTextView = NO;
131 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.terminal"))) {
132 ICCF_PatchMethod("TermSubview", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:") &&
133 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "selectedRange") &&
134 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "attributedSubstringFromRange:") &&
135 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseDown:") &&
136 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseUp:") &&
137 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "draggingEntered:") &&
138 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "_optionClickEvent:::");
139 ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal\n");
140 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("org.mozilla.camino"))) {
141 ICCF_PatchMethod("ChildView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
142 ICapeprintf("ICeCoffEE APE: loaded in ChildView for Camino\n");
143 }
144 }
145
146 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
147 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
148 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:");
149 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for WebKit/Safari\n");
150
151 if (shouldLoadInNSTextView) {
152 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
153 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:");
154 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
155 }
156
157 ICCF_ReloadPrefs();
158
159 return;
160}
161
162// 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.
163OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
164{
165 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
166
167 if (CFStringCompare(message, kICPreferencesChanged, 0) == kCFCompareEqualTo)
168 { // request to reload prefs from our preference pane
169 ICCF_ReloadPrefs();
170 }
171
172 return noErr;
173}
174
175Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
176 Boolean keyExists;
177 Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
178 if (keyExists) return value;
179 CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
180 return defaultValue;
181}
182
183CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
184 Boolean keyExists;
185 CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
186 if (keyExists) return value;
187 CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
188 CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
189 CFRelease(defaultValueNumber);
190 return defaultValue;
191}
192
193void ICCF_GetCFTypePref(CFStringRef prefKey, CFTypeRef *val, CFTypeID type) {
194 if (*val != NULL) {
195 CFRelease(*val);
196 *val = NULL;
197 }
198
199 *val = CFPreferencesCopyAppValue(prefKey, kICBundleIdentifier);
200
201 if (*val == NULL) return;
202
203 if (CFGetTypeID(*val) != type) {
204 CFRelease(*val);
205 *val = NULL;
206 return;
207 }
208}
209
210// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
211static void ICCF_MigratePrefs() {
212
213 CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
214
215 if (prefExcludedApps == NULL) return;
216
217 if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
218 CFRelease(prefExcludedApps);
219 return;
220 }
221
222 CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
223 CFRelease(prefExcludedApps); prefExcludedApps = NULL;
224
225 ICapeprintf("Excluded apps: %@\n", excludedApps);
226
227 CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
228 CFIndex excludedAppIndex;
229
230 CFDictionaryRef excludedAppSpecifiers = NULL;
231 CFStringRef excludedAppBundleID = NULL;
232 CFURLRef excludedAppURL = NULL;
233 CFBundleRef excludedAppBundle = NULL;
234
235 BOOL postponeMigrationForApp;
236
237 for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
238 if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
239 CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
240 continue;
241 }
242
243 postponeMigrationForApp = NO;
244 if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
245 && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
246 && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
247 if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
248 excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
249 if (excludedAppBundle != NULL) {
250 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle, CFSTR("Migrated from ICeCoffEE 1.0-1.2"), NO);
251 } else postponeMigrationForApp = YES; // can't create bundle
252 } else postponeMigrationForApp = YES; // can't find app
253 }
254 // don't release excludedAppSpecifiers, used Get
255 // don't release excludedAppBundleID, used Get
256 if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
257 if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
258 if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
259 }
260
261 ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
262 CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
263 CFRelease(excludedApps);
264
265 CFPreferencesAppSynchronize(kICBundleIdentifier);
266}
267
268static void ICCF_ReloadPrefs() {
269 CFPreferencesAppSynchronize(kICBundleIdentifier);
270
271 ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
272 ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
273 ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
274 ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
275 ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
276 ICCF_GetCFTypePref(kICServiceOptions, (CFTypeRef *)&ICCF_prefs.serviceOptions, CFDictionaryGetTypeID());
277 ICCF_prefs.terminalRequireOptionForSelfDrag = ICCF_GetBooleanPref(kICTerminalRequireOptionForSelfDrag, NO);
278 ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
279 ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
280
281 CFPreferencesAppSynchronize(kICBundleIdentifier);
282
283 ICCF_AddRemoveServicesMenu();
284}
Note: See TracBrowser for help on using the repository browser.