source: trunk/Cocoa/F-Script Anywhere/Source/FSAApp.mm@ 228

Last change on this file since 228 was 222, checked in by rchin, 18 years ago

Fixed problem with F-Script leaking file handles (caused F-Script to not be able to inject into applications after a certain number of running apps had been launched). The problem was in appIsPEF calling open(2) but not close(2) before returning.

File size: 11.7 KB
Line 
1//
2// FSAApp.m
3// F-Script Anywhere
4//
5// Created by Nicholas Riley on Fri Feb 01 2002.
6// Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9/*
10
11 F-Script Anywhere is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 F-Script Anywhere is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with F-Script Anywhere; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
25*/
26
27#import "FSAApp.h"
28#import "FSAnywhere.h"
29
30NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";
31NSString * const PatchBundleName = @"F-Script Anywhere.bundle";
32
33typedef struct {
34 OSStatus err;
35 NSString * const desc;
36} errRec, errList[];
37
38// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
39static errList ERRS = { // XXX change for mach_inject/SCPatch
40 { 5, @"F-Script Anywhere must be installed in a Cocoa application running as the current user.\n\nYou may be attempting to install in a setuid application, which is not supported" },
41 { 11, @"F-Script Anywhere cannot be installed in itself.\n\nIf you wish to install F-Script Anywhere in itself, make a copy of the F-Script Anywhere application and install one copy into the other" },
42 { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script AnywhereÕs dock menu to install" },
43 { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application. Please try reinstalling F-Script Anywhere" },
44 { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
45 { 0, nil }
46};
47
48NSMutableDictionary *FSA_errors;
49
50NSString * FSA_descriptionForOSStatus(OSStatus err)
51{
52 NSString *desc = nil;
53
54 if (FSA_errors != nil)
55 desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]];
56
57 if (desc != nil)
58 return desc;
59
60 return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err];
61}
62
63FSAPatchController::FSAPatchController(FSAApp *fsaApp)
64{
65 mApp = fsaApp;
66}
67
68void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) {
69 pid_t pid;
70 OSStatus err = GetProcessPID(inPSN, &pid);
71
72 NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err);
73
74 FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid);
75 [mApp controllerIsPatchingApplicationWithProcessID: pid];
76}
77
78// XXX should remove?
79void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) {
80 OSErr err = noErr;
81 Size actualSize;
82 DescType actualType;
83 OSType eventClass, eventID;
84
85 if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
86 return;
87 if (eventClass != kSCMessageClass)
88 return;
89
90 if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr)
91 return;
92
93#if 0
94 if (eventID == kSCLoadResult) {
95 OSStatus
96 (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
97 (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
98 bundleID[actualSize] = 0;
99 printf("patch bundle %s loaded with error status %d\n", bundleID, error);
100 }
101#endif
102
103
104}
105
106
107@implementation FSAApp
108
109+ (void)initialize;
110{
111 errRec *rec;
112
113 FSA_errors = [[NSMutableDictionary alloc] init];
114 for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
115 [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
116}
117
118- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result;
119{
120 switch (result) {
121 case NSAlertOtherReturn:
122 return true;
123 case NSAlertAlternateReturn:
124 [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FSA_FScriptURL]];
125 case NSAlertDefaultReturn:
126 default:
127 [self terminate: self];
128 return false;
129 }
130}
131
132- (void)finishLaunching
133{
134 patchController = new FSAPatchController(self);
135 patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
136 (CFStringRef)PatchBundleName);
137
138 NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
139 pid_t pid;
140 ProcessSerialNumber psn;
141 OSStatus err;
142
143 while ( (pid = [[e nextObject] longValue]) != 0) {
144 err = GetProcessForPID(pid, &psn);
145
146 if (err != noErr) {
147 NSLog(@"Can't get PSN for PID %ld", pid);
148 [appList applicationQuitWithProcessID: pid];
149 } else if (patchController->IsProcessPatched(&psn)) {
150 [appList didPatchProcessID: pid];
151 }
152 }
153
154 // yes, someone could move the framework while the app is running, but they deserve what they get.
155 NSFileManager *fileMgr = [NSFileManager defaultManager];
156 NSArray *libraryDirectories =
157 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
158 NSString *libPath;
159 NSString *frameworkPath;
160 CFBundleRef frameworkBundle = NULL;
161 BOOL isDirectory;
162
163 e = [libraryDirectories objectEnumerator];
164 while ( (libPath = [e nextObject]) != nil) {
165 frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
166 stringByAppendingPathComponent: @"FScript.framework"];
167 if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
168 frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
169 [(id)frameworkBundle autorelease];
170 break;
171 }
172 }
173 FSALog(@"Framework bundle");
174 FSAShow(frameworkBundle);
175 [super finishLaunching];
176 if (frameworkBundle == NULL) {
177 int result = NSRunInformationalAlertPanel(
178 NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
179 NSLocalizedString(@"F-Script Anywhere requires the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks.\n\nTo download F-Script, please visit its Web site\n%@.\n\nIf you believe this message is in error, click '%@'.",
180 "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
181 NSLocalizedString(@"Quit", "Quit button title"),
182 NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
183 NSLocalizedString(@"Continue", "Continue button title"),
184 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
185 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
186 } else {
187 UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
188 FSALog(@"Framework version 0x%x", version);
189 if (version < FSA_FSCRIPT_MIN_VERSION) {
190 NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
191 if (frameworkVersion == nil)
192 frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
193 int result = NSRunInformationalAlertPanel(
194 NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
195 NSLocalizedString(@"F-Script Anywhere %s requires version %@ or later of the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks. The installed version is %@.\n\nTo upgrade F-Script, please visit its Web site\n%@.\n\nTo use this version of F-Script regardless of the problems you may experience, click '%@'.",
196 "Framework too old alert message, FSA version parameter, minimum F-Script version parameter, current F-Script version parameter, F-Script Web site URL parameter, 'Continue' button parameter"),
197 NSLocalizedString(@"Quit", "Quit button title"),
198 NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
199 NSLocalizedString(@"Continue", "Continue button title"),
200 FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
201 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
202 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
203 }
204 }
205
206 NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
207 [workspaceNotificationCenter
208 addObserver: self
209 selector: @selector(applicationDidLaunch:)
210 name: NSWorkspaceWillLaunchApplicationNotification
211 object: nil];
212 [workspaceNotificationCenter
213 addObserver: self
214 selector: @selector(applicationDidTerminate:)
215 name: NSWorkspaceDidTerminateApplicationNotification
216 object: nil];
217
218 [appListWindow makeKeyAndOrderFront: self];
219}
220
221- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
222{
223 NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
224 nil, nil, nil, appListWindow, self, nil, nil, nil,
225 NSLocalizedString(@"F-Script Anywhere was unable to install itself in the selected application (process ID %d), because %@.\n\nThe application may have crashed; restart it if needed.", "Unable to install alert message, PID parameter, 'XXX happened because' explanation parameter"),
226 pid, FSA_descriptionForOSStatus(err));
227}
228
229- (void)installBundleInAppWithPID:(pid_t)pid;
230{
231 if (pid == -1) return;
232
233 ProcessSerialNumber psn;
234 OSStatus err = GetProcessForPID(pid, &psn);
235
236 if (err == noErr)
237 err = patchController->PatchProcess(&psn);
238
239 if (err != noErr)
240 [self installationError: err inAppWithPID: pid];
241 else
242 [appList didPatchProcessID: pid];
243}
244
245- (void)loaderBundleMessage:(NSNotification *)notification;
246{
247 /*
248 if (error) {
249 [self installationError: error inAppWithPID: pid];
250 } else {
251 [appList didPatchProcessID: pid];
252 } */
253}
254
255- (IBAction)installBundleInSelectedApp:(id)sender;
256{
257 [self installBundleInAppWithPID: [appList selectedProcessID]];
258}
259
260// from dock menu only!
261- (IBAction)installBundleInFrontmostApp:(id)sender;
262{
263 NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
264 [self installBundleInAppWithPID: (pid_t)[sender tag]];
265}
266
267@end
268
269@implementation FSAApp (FSAPatchControllerDelegate)
270
271- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
272{
273 [appList isPatchingProcessID: pid];
274}
275
276@end
277
278@implementation FSAApp (NSWorkspaceNotifications)
279
280- (void)applicationDidLaunch:(NSNotification *)notification;
281{
282 [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
283}
284
285- (void)applicationDidTerminate:(NSNotification *)notification;
286{
287 [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
288}
289
290@end
Note: See TracBrowser for help on using the repository browser.