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

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

ICeCoffEE 1.4.2b1

VERSION, ui.plist, Info*.plist, InfoPlist.strings: Updated for 1.4.2b1.

APEMain.m: Removed PBX support.

ICeCoffEE.[hm]: Don't ICCF_OSErr(C)Assert if we get userCanceledErr:
part of fixing extraneous exception when not selecting anything from
the helper app menu. ICCF_KeyboardAction() and
ICCF_LaunchURLFromTextView() now take an event parameter - since we
have stuff on a timer now, means we get the key modifier state at
mousedown time, rather than some arbitrary time later.
ICCF_LaunchURL() returns NO if the user cancelled, so we don't need to
throw an exception to stop the URL blinking. Moved sanitized mouse
down event generation to ICCF_MouseDownEventWithModifierFlags(). Only
update Services menu at app launch on Panther. Use a timer to delay
URL launching in NSTextView on Tiger, so we accommodate
command-multiple clicking for discontiguous selection. Remove that
ugly goto.

ICeCoffEEShared.h: Turn off debugging in preparation for (beta)
release.

ICeCoffEETerminal.m: Update for new ICCF_LaunchURL() return.

ICeCoffEETrigger.[hm]: Singleton timer wrapper for discontiguous
selection compatibility on Tiger. Singleton global is exported for
efficiency since we have to check it on every mouse down.

ICeCoffEEWebKit.m: Removed incorrect comment (what was I thinking?)
Update for new ICCF_LaunchURL() return. Properly highlight before
ICCF_LaunchURL(), especially noticable with menu.

APEInfo.rtfd: More fixes and updates, final for 1.4.2b1.

File size: 12.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
69// 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 appllications.
70Boolean ICCF_shouldLoad;
71
72void APEBundleMainEarlyLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
73{
74 ICCF_MigratePrefs();
75
76 UInt32 icVersion = CFBundleGetVersionNumber(inBundle);
77 ICapeprintf("ICeCoffEE APE: bundle version is %ld (0x%x)\n", icVersion, icVersion);
78 CFNumberRef icVersionRef = CFNumberCreate(NULL, kCFNumberLongType, &icVersion);
79 CFPreferencesSetAppValue(kICLastLoadedVersion, icVersionRef, kICBundleIdentifier);
80 SAFE_RELEASE(icVersionRef);
81
82 ICCF_shouldLoad = false;
83
84 CPSProcessSerNum psn;
85 OSStatus err = CPSGetCurrentProcess(&psn);
86 if (err != noErr) {
87#if ICCF_DEBUG
88 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); still loading because of ICCF_DEBUG\n", err);
89#else
90 apeprintf("ICeCoffEE APE: Can't get process serial number for current process (error %ld); not loading in this application\n", err);
91 return;
92#endif
93 }
94
95 CPSProcessInfoRec info;
96 err = CPSGetProcessInfo(&psn, &info, NULL, 0, NULL, NULL, 0);
97 if (err != noErr) {
98#if ICCF_DEBUG
99 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); still loading because of ICCF_DEBUG\n", err);
100#else
101 apeprintf("ICeCoffEE APE: Can't get process information (error %ld); not loading in this application\n", err);
102 return;
103#endif
104 } else {
105 ICapeprintf("ICeCoffEE APE: got process attributes = 0x%lx\n", info.Attributes);
106 if (info.Attributes & (kCPSBGOnlyAttr | kCPSUIElementAttr | kCPSFullScreenAttr)) {
107 ICapeprintf("ICeCoffEE APE: not loading as this application is background-only\n");
108 return;
109 }
110 }
111 ICCF_shouldLoad = true;
112}
113
114Boolean ICCF_CFBundleIDMatches(CFStringRef bundleID, CFStringRef test) {
115 return CFStringCompare(bundleID, test, kCFCompareCaseInsensitive) == kCFCompareEqualTo;
116}
117
118void APEBundleMainLateLoad(CFBundleRef inBundle, CFStringRef inAPEToolsApplicationID)
119{
120 if (!ICCF_shouldLoad) return;
121
122 ICCF_bundle = CFBundleGetBundleWithIdentifier(kICBundleIdentifier);
123 CFStringRef bundleID = CFBundleGetIdentifier(CFBundleGetMainBundle());
124 BOOL shouldLoadInNSTextView = YES;
125
126 // XXX handle patching error return from ICCF_PatchMethod
127 if (bundleID != NULL) {
128 if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.xcode"))) {
129 ICCF_PatchMethod("PBXTextView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
130 ICapeprintf("ICeCoffEE APE: loaded in PBXTextView for PB!\n");
131 shouldLoadInNSTextView = NO;
132 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("com.apple.terminal"))) {
133 ICCF_PatchMethod("TermSubview", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:") &&
134 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "selectedRange") &&
135 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "attributedSubstringFromRange:") &&
136 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseDown:") &&
137 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "mouseUp:") &&
138 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "draggingEntered:") &&
139 ICCF_PatchMethod("TermSubview", "ICeCoffEETerminal", "ICECoffEETermSubviewSuper", "_optionClickEvent:::") ,
140 ICapeprintf("ICeCoffEE APE: loaded in TermSubview for Terminal!\n");
141 } else if (ICCF_CFBundleIDMatches(bundleID, CFSTR("org.mozilla.navigator"))) {
142 ICCF_PatchMethod("ChildView", "ICeCoffEEMenuOnly", "ICeCoffEEMenuSuper", "menuForEvent:");
143 ICapeprintf("ICeCoffEE APE: loaded in ChildView for Camino\n");
144 }
145 }
146
147 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseUp:") &&
148 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "mouseDown:") &&
149 ICCF_PatchMethod("WebHTMLView", "ICeCoffEEWebKit", "ICeCoffEEWebKitSuper", "menuForEvent:"),
150 ICapeprintf("ICeCoffEE APE: loaded in WebHTMLView for WebKit/Safari\n");
151
152 if (shouldLoadInNSTextView) {
153 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "mouseDown:") &&
154 ICCF_PatchMethod("NSTextView", "ICeCoffEE", "ICeCoffEESuper", "menuForEvent:") ,
155 ICapeprintf("ICeCoffEE APE: loaded generic NSTextView support\n");
156 }
157
158 gTEClick = APEPatchCreate(&TEClick, &ICCF_TEClick);
159 if (gTEClick != NULL) {
160 ICapeprintf("ICeCoffEE APE: patched TEClick\n");
161 }
162
163 ICCF_ReloadPrefs();
164
165 return;
166}
167
168// 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.
169OSStatus APEBundleMessage(CFStringRef message,CFDataRef inData,CFDataRef *outData)
170{
171 ICapeprintf("ICeCoffEE APE: message '%@' (inData = %@)\n", message, inData);
172
173 if (CFStringCompare(message, kICPreferencesChanged, 0) == kCFCompareEqualTo)
174 { // request to reload prefs from our preference pane
175 ICCF_ReloadPrefs();
176 }
177
178 return noErr;
179}
180
181Boolean ICCF_GetBooleanPref(CFStringRef prefKey, Boolean defaultValue) {
182 Boolean keyExists;
183 Boolean value = CFPreferencesGetAppBooleanValue(prefKey, kICBundleIdentifier, &keyExists);
184 if (keyExists) return value;
185 CFPreferencesSetAppValue(prefKey, defaultValue ? kCFBooleanTrue : kCFBooleanFalse, kICBundleIdentifier);
186 return defaultValue;
187}
188
189CFIndex ICCF_GetCFIndexPref(CFStringRef prefKey, CFIndex defaultValue) {
190 Boolean keyExists;
191 CFIndex value = CFPreferencesGetAppIntegerValue(prefKey, kICBundleIdentifier, &keyExists);
192 if (keyExists) return value;
193 CFNumberRef defaultValueNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &defaultValue);
194 CFPreferencesSetAppValue(prefKey, defaultValueNumber, kICBundleIdentifier);
195 CFRelease(defaultValueNumber);
196 return defaultValue;
197}
198
199void ICCF_GetCFTypePref(CFStringRef prefKey, CFTypeRef *val, CFTypeID type) {
200 if (*val != NULL) {
201 CFRelease(*val);
202 *val = NULL;
203 }
204
205 *val = CFPreferencesCopyAppValue(prefKey, kICBundleIdentifier);
206
207 if (*val == NULL) return;
208
209 if (CFGetTypeID(*val) != type) {
210 CFRelease(*val);
211 *val = NULL;
212 return;
213 }
214}
215
216// to test: defaults write net.sabi.ICeCoffEE "Excluded Applications" -array '{CFBundleID = "com.apple.projectbuilder"; }' '{CFBundleID = "net.sabi.Pester"; }' '{CFBundleID = "com.apple.foobar"; }'
217static void ICCF_MigratePrefs() {
218
219 CFArrayRef prefExcludedApps = CFPreferencesCopyAppValue(kIC12PrefExcluded, kICBundleIdentifier);
220
221 if (prefExcludedApps == NULL) return;
222
223 if (CFGetTypeID(prefExcludedApps) != CFArrayGetTypeID()) {
224 CFRelease(prefExcludedApps);
225 return;
226 }
227
228 CFMutableArrayRef excludedApps = CFArrayCreateMutableCopy(NULL, 0, prefExcludedApps);
229 CFRelease(prefExcludedApps); prefExcludedApps = NULL;
230
231 ICapeprintf("Excluded apps: %@\n", excludedApps);
232
233 CFIndex excludedAppCount = CFArrayGetCount(excludedApps);
234 CFIndex excludedAppIndex;
235
236 CFDictionaryRef excludedAppSpecifiers = NULL;
237 CFStringRef excludedAppBundleID = NULL;
238 CFURLRef excludedAppURL = NULL;
239 CFBundleRef excludedAppBundle = NULL;
240
241 BOOL postponeMigrationForApp;
242
243 for (excludedAppIndex = excludedAppCount - 1 ; excludedAppIndex >= 0 ; excludedAppIndex--) {
244 if ( (excludedAppSpecifiers = CFArrayGetValueAtIndex(excludedApps, excludedAppIndex)) == NULL || CFGetTypeID(excludedAppSpecifiers) != CFDictionaryGetTypeID()) {
245 CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
246 continue;
247 }
248
249 postponeMigrationForApp = NO;
250 if ( (excludedAppBundleID = CFDictionaryGetValue(excludedAppSpecifiers, kIC12PrefExcludedAppSpecifierBundleID)) != NULL
251 && CFGetTypeID(excludedAppBundleID) == CFStringGetTypeID()
252 && CFStringCompare(excludedAppBundleID, CFSTR("com.apple.projectbuilder"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) {
253 if (LSFindApplicationForInfo(kLSUnknownCreator, excludedAppBundleID, NULL, NULL, &excludedAppURL) == noErr) {
254 excludedAppBundle = CFBundleCreate(NULL, excludedAppURL);
255 if (excludedAppBundle != NULL) {
256 APEToolsAddToExcludeList(kICBundleIdentifier, excludedAppBundle, CFSTR("Migrated from ICeCoffEE 1.0-1.2"), NO);
257 } else postponeMigrationForApp = YES; // can't create bundle
258 } else postponeMigrationForApp = YES; // can't find app
259 }
260 // don't release excludedAppSpecifiers, used Get
261 // don't release excludedAppBundleID, used Get
262 if (excludedAppURL != NULL) { CFRelease(excludedAppURL); excludedAppURL = NULL; }
263 if (excludedAppBundle != NULL) { CFRelease(excludedAppBundle); excludedAppBundle = NULL; }
264 if (!postponeMigrationForApp) CFArrayRemoveValueAtIndex(excludedApps, excludedAppIndex);
265 }
266
267 ICapeprintf("Excluded apps remaining: %@\n", excludedApps);
268 CFPreferencesSetAppValue(kIC12PrefExcluded, (CFArrayGetCount(excludedApps) == 0 ? NULL : excludedApps), kICBundleIdentifier);
269 CFRelease(excludedApps);
270
271 CFPreferencesAppSynchronize(kICBundleIdentifier);
272}
273
274static void ICCF_ReloadPrefs() {
275 CFPreferencesAppSynchronize(kICBundleIdentifier);
276
277 ICCF_prefs.commandClickEnabled = ICCF_GetBooleanPref(kICCommandClickEnabled, YES);
278 ICCF_prefs.textBlinkEnabled = ICCF_GetBooleanPref(kICTextBlinkEnabled, YES);
279 ICCF_prefs.textBlinkCount = ICCF_GetCFIndexPref(kICTextBlinkCount, 3);
280 ICCF_prefs.servicesInContextualMenu = ICCF_GetBooleanPref(kICServicesInContextualMenu, YES);
281 ICCF_prefs.servicesInMenuBar = ICCF_GetBooleanPref(kICServicesInMenuBar, NO);
282 ICCF_GetCFTypePref(kICServiceOptions, (CFTypeRef *)&ICCF_prefs.serviceOptions, CFDictionaryGetTypeID());
283 ICCF_prefs.terminalRequireOptionForSelfDrag = ICCF_GetBooleanPref(kICTerminalRequireOptionForSelfDrag, NO);
284 ICCF_prefs.errorSoundEnabled = ICCF_GetBooleanPref(kICErrorSoundEnabled, NO);
285 ICCF_prefs.errorDialogEnabled = ICCF_GetBooleanPref(kICErrorDialogEnabled, YES);
286
287 CFPreferencesAppSynchronize(kICBundleIdentifier);
288
289 ICCF_AddRemoveServicesMenu();
290}
Note: See TracBrowser for help on using the repository browser.