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

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

APEMain.m: Note missing Xcode 3 support. Add Terminal 2.0 support.

English.lproj/APE Manager plugin.nib:

English.lproj/APEInfo.rtfd:

ICeCoffEE.[hm]: Bring triggering window and app to front if error
dialog displayed in ICCF_HandleException. Remove initial word
selection. Launch preexisting selection if present. Remove 10.3
support. Update for new ICeCoffEETrigger API. Rename downEvent
parameter for consistency.

ICeCoffEE.xcodeproj: Added files.

ICeCoffEEParser.m: Handle multiline URLs.

ICeCoffEETTView.[hm]: Terminal 2.0 support (thank you for having a
usable NSTextInput implementation!)

ICeCoffEETTViewTrigger.[hm]: ICeCoffEETrigger implementation for
TTView. Can't set range as we do for NSTextView because you can't
have an arbitrary empty selection range in Terminal, so we pass it
directly to ICCF_LaunchURLFromTTView.

ICeCoffEETerminal.[hm]: Fix capitalization on ICeCoffEETermSubviewSuper.
Pass downEvent to ICCF_HandleException so it can bring the triggering
window to the front.

ICeCoffEETextViewTrigger.[hm]: Moved from ICeCoffEETrigger.

ICeCoffEETrigger.[hm]: Now an abstract superclass. Replace direct
access to ICCF_sharedTrigger with +cancel. Create 0-character range
(if click outside selectedRange) or save selectedRange. Move
debugging logs here from ICeCoffEE.m. Add description method.

ICeCoffEEWebKit.m: Pass downEvent to ICCF_HandleException so it can
bring the triggering window to the front.

Installer components/ui/ui.plist: Updated for 1.5d3.

TestParser.m: Handle multiline URLs; newlines are printed as \ and
tabs as >. Implement ICCF_StringByRemovingCharactersInSet, which will
move elsewhere once Internet Config support is removed.

VERSION.xcconfig: Updated for 1.5d3.

urls.plist: Test for multiline URLs.

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