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

Last change on this file since 153 was 153, checked in by Nicholas Riley, 18 years ago

Integrates SCPatch and mach_inject; unfinished, buggy.

File size: 11.6 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 (eventID == kSCLoadResult) {
94 OSStatus
95 (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
96 (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
97 bundleID[actualSize] = 0;
98 printf("patch bundle %s loaded with error status %d\n", bundleID, error);
99 }
100
101
102}
103
104
105@implementation FSAApp
106
107+ (void)initialize;
108{
109 errRec *rec;
110
111 FSA_errors = [[NSMutableDictionary alloc] init];
112 for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
113 [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
114}
115
116- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result;
117{
118 switch (result) {
119 case NSAlertOtherReturn:
120 return true;
121 case NSAlertAlternateReturn:
122 [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FSA_FScriptURL]];
123 case NSAlertDefaultReturn:
124 default:
125 [self terminate: self];
126 return false;
127 }
128}
129
130- (void)finishLaunching
131{
132 patchController = new FSAPatchController(self);
133 patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
134 (CFStringRef)PatchBundleName);
135
136 NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
137 pid_t pid;
138 ProcessSerialNumber psn;
139 OSStatus err;
140
141 while ( (pid = [[e nextObject] longValue]) != 0) {
142 err = GetProcessForPID(pid, &psn);
143
144 if (err != noErr) {
145 NSLog(@"Can't get PSN for PID %ld", pid);
146 [appList applicationQuitWithProcessID: pid];
147 } else if (patchController->IsProcessPatched(&psn)) {
148 [appList didPatchProcessID: pid];
149 }
150 }
151
152 // yes, someone could move the framework while the app is running, but they deserve what they get.
153 NSFileManager *fileMgr = [NSFileManager defaultManager];
154 NSArray *libraryDirectories =
155 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
156 NSString *libPath;
157 NSString *frameworkPath;
158 CFBundleRef frameworkBundle = NULL;
159 BOOL isDirectory;
160
161 e = [libraryDirectories objectEnumerator];
162 while ( (libPath = [e nextObject]) != nil) {
163 frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
164 stringByAppendingPathComponent: @"FScript.framework"];
165 if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
166 frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
167 [(id)frameworkBundle autorelease];
168 break;
169 }
170 }
171 FSALog(@"Framework bundle");
172 FSAShow(frameworkBundle);
173 [super finishLaunching];
174 if (frameworkBundle == NULL) {
175 int result = NSRunInformationalAlertPanel(
176 NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
177 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 '%@'.",
178 "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
179 NSLocalizedString(@"Quit", "Quit button title"),
180 NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
181 NSLocalizedString(@"Continue", "Continue button title"),
182 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
183 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
184 } else {
185 UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
186 FSALog(@"Framework version 0x%x", version);
187 if (version < FSA_FSCRIPT_MIN_VERSION) {
188 NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
189 if (frameworkVersion == nil)
190 frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
191 int result = NSRunInformationalAlertPanel(
192 NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
193 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 '%@'.",
194 "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"),
195 NSLocalizedString(@"Quit", "Quit button title"),
196 NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
197 NSLocalizedString(@"Continue", "Continue button title"),
198 FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
199 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
200 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
201 }
202 }
203
204 NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
205 [workspaceNotificationCenter
206 addObserver: self
207 selector: @selector(applicationDidLaunch:)
208 name: NSWorkspaceWillLaunchApplicationNotification
209 object: nil];
210 [workspaceNotificationCenter
211 addObserver: self
212 selector: @selector(applicationDidTerminate:)
213 name: NSWorkspaceDidTerminateApplicationNotification
214 object: nil];
215
216 [appListWindow makeKeyAndOrderFront: self];
217}
218
219- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
220{
221 NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
222 nil, nil, nil, appListWindow, self, nil, nil, nil,
223 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"),
224 pid, FSA_descriptionForOSStatus(err));
225}
226
227- (void)installBundleInAppWithPID:(pid_t)pid;
228{
229 if (pid == -1) return;
230
231 ProcessSerialNumber psn;
232 OSStatus err = GetProcessForPID(pid, &psn);
233
234 if (err == noErr)
235 err = patchController->PatchProcess(&psn);
236
237 if (err != noErr)
238 [self installationError: err inAppWithPID: pid];
239}
240
241- (void)loaderBundleMessage:(NSNotification *)notification;
242{
243 /*
244 if (error) {
245 [self installationError: error inAppWithPID: pid];
246 } else {
247 [appList didPatchProcessID: pid];
248 } */
249}
250
251- (IBAction)installBundleInSelectedApp:(id)sender;
252{
253 [self installBundleInAppWithPID: [appList selectedProcessID]];
254}
255
256// from dock menu only!
257- (IBAction)installBundleInFrontmostApp:(id)sender;
258{
259 NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
260 [self installBundleInAppWithPID: (pid_t)[sender tag]];
261}
262
263@end
264
265@implementation FSAApp (FSAPatchControllerDelegate)
266
267- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
268{
269 [appList isPatchingProcessID: pid];
270}
271
272@end
273
274@implementation FSAApp (NSWorkspaceNotifications)
275
276- (void)applicationDidLaunch:(NSNotification *)notification;
277{
278 [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
279}
280
281- (void)applicationDidTerminate:(NSNotification *)notification;
282{
283 [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
284}
285
286@end
Note: See TracBrowser for help on using the repository browser.