I'm running into an issue with relaunching my application on 10.5. In my Info.plist I have LSMinimumSystemVersionByArchitecture set so that the application will run in 64-bit for x86_64 and 32-bit on i386, ppc, and ppc64.
I have a preference in the app that allows the user to switch between a Dock icon & NSStatusItem, and it prompts the user to relaunch the app once they change the setting using using the following code:
id fullPath = [[NSBundle mainBundle] executablePath];
NSArray *arg = [NSArray arrayWithObjects:nil];
[NSTask launchedTaskWithLaunchPath:fullPath arguments:arg];
[NSApp terminate:self];
When this executes on 10.5, however it relaunches the application in 64-bit which is not a desired result for me. From what I gather reading the docs its because the LS* keys are not read when the app is launched via command line.
Is there a way around this? I tried doing something like below, which worked on 10.6, but on 10.5 it was chirping at me that the "launch path not accessible". ([NSApp isOnSnowLeopardOrBetter] is a category that checks the AppKit version number).
id path = [[NSBundle mainBundle] executablePath];
NSString *fullPath = nil;
if (![NSApp isOnSnowLeopardOrBetter])
fullPath = [NSString stringWithFormat:#"/usr/bin/arch -i386 -ppc %#", path];
else
fullPath = path;
NSArray *arg = [NSArray arrayWithObjects:nil];
[NSTask launchedTaskWithLaunchPath:fullPath arguments:arg];
[NSApp terminate:self];
You should instead use the methods of NSWorkspace, which does take into account Info.plist keys. For example, use -(BOOL)launchApplication:(NSString*).
It's because you use spaces inside fullpath, use arguments within an array [NSArray arrayWithObjects:#"/usr/bin/arch",#"-i386",#"-ppc",path,nil].
try this code for relaunch your app:
//terminate your app in some of your method:
[[NSApplication sharedApplication]terminate:nil];
- (void)applicationWillTerminate:(NSNotification *)notification {
if (i need to restart my app) {
NSTask *task = [NSTask new];
[task setLaunchPath:#"/usr/bin/open"];
[task setArguments:[NSArray arrayWithObjects:#"/Applications/MyApp.app", nil]];
[task launch];
}
}
Related
I'm having some trouble getting this NSTask to launch from an NSPopover view. The popover I created has several things in it. First the user selects a path using NSOpenPanel, then the user enters a username so that I can construct a path to transfer data to. Finally, there's a button that is supposed to launch the NSTask that initiates the data transfer. I had to add some extra code to make the text field work for user input. See the following link for what I followed to get the text field to work: NSStatusItem with NSPopover and NSTextField
Any reason why this wouldn't work? This code had been working when it was contained in a NSWindow.
- (IBAction)beginTransfer:(id)sender {
//Construct a string of the user selected source path.
NSString *userAccountPath = [_sourcePathTextField stringValue];
//Check that I got the expected result
NSLog (#"%#", userAccountPath);
//Create an array with the pieces I need to construct a destination account path
NSArray *strings = [NSArray arrayWithObjects:#"/Users/", [userAccountTextField stringValue], nil];
//Construct a string destination account location from the pieces
NSString *userAccountDestinationPath = [strings componentsJoinedByString:#""];
//Make sure the construction is correct
NSLog (#"%#", userAccountDestinationPath);
//Setup NSTask
NSTask *transferFiles;
transferFiles = [[NSTask alloc] init];
//Arguements for rsync
NSArray *arguements;
arguements = [NSArray arrayWithObjects:#"--paH", #"--info=progress2", #"--human-readable", #"--exclude", #"/Library/Keychains", userAccountPath, userAccountDestinationPath, nil];
//Setup output pipe
NSPipe *transferOutput = [NSPipe pipe];
[transferFiles setStandardOutput:transferOutput];
//Launch rsync with arguements from above
[transferFiles setArguments:arguements];
[transferFiles setLaunchPath:#"/usr/local/bin/rsync"];
[transferFiles launch];
NSFileHandle *fh = [transferOutput fileHandleForReading];
[fh waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedData:) name:#"NSFileHandleDataAvailableNotification" object:nil];
[transferFiles waitUntilExit];
[transferFiles release];
[fh release];
}
I mounted the hard drive by using the GUI in Mac.
However, I want to mount the hard drive by using the terminal commands.
How can I execute a terminal command mount_smbfs from my Objective-C Cocoa application?
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:#"/sbin/mount_smbfs"];
[task setArguments:[NSArray arrayWithObjects:#"//user:50000#smb://192.168.2.1/Share",#"Volumes/C$/upload", nil]];
[task launch];
Here is my edited with my code Could you please help me?
You can wrap the call to mount_smbfs in NSTask to execute it from your Obj-C program:
NSTask* task = [NSTask new];
[task setLaunchPath:#"/sbin/mount_smbfs"];
[task setArguments:[NSArray arrayWithObjects:#"//myUser:myPassword#SERVER/share", #"mountPath", nil]];
In setArguments you provide an array with at least 2 elements: the path to the share, and the mount point.
Also check man mount_smbfs for more argument options.
It's simpler to do this with AppleScript:
- (BOOL) mount {
NSAppleScript *script = [[NSAppleScript alloc] initWithSource:
#"tell application \"Finder\"\n"
" mount volume \"smb://server.domain/SomeMountPoint\"\n"
"end tell"];
if (!script) {
NSLog(#"Error creating AppleScript object");
return NO;
}
NSDictionary *errorMessage = nil;
NSAppleEventDescriptor *result = [script executeAndReturnError:&errorMessage];
return (BOOL)result;
}
There are some limitations:
You have to use NSAppleScript on the main thread.
Your application won't respond to any events while Finder tries to mount the volume.
If mounting fails, you don't have any control over how Finder presents the error message.
I want to run some lines of applescript code from within my application. The standard way is to use the NSAppleScript class. However, because that code might take a few minutes to complete, I have to use a separate thread or the interface will stop. The big problem is, as it says here, the NSAppleScript class can ONLY be run on the main thread.
So, if I run the code on a separate thread, my app crashes; If I run it on the main thread, it stops. Any ideas?
Also, I considered using NSTask and the osascript command, but I saw somewhere (can't find the link) that osascript doesn't support user input such as dialog boxes and stuff. I'm not sure if that's true, but if it is then osascript is not a solution.
So I ended up writing a helper for this, launched via NSTask. Here is the code, in case anyone's interested:
For the launcher:
NSArray* args = [NSArray arrayWithObject: <<AS code here>>];
task = [NSTask new];
[task setLaunchPath: [[NSBundle mainBundle] pathForResource: #"ASHelper" ofType: #""]];
[task setArguments: args];
[task launch];
And the helper:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSString* source = [NSString stringWithCString: argv[1] length: strlen(argv[1])];
NSAppleScript* as = [[NSAppleScript alloc] initWithSource: source];
[as executeAndReturnError: nil];
}
return 0;
}
I have the following code (below) in my app, which simply tries to use NSTask to touch one file with the directory time/date stamp. It works just fine in an app which only accesses one directory all the time, however, it doesn't with another that frequently changes directories to access some of it's data. When I check the currentfile and currentpath they both show the correct paths. I've expired every possibility I can think of; any help would be great appreciated — thank you.
- (void)someMethod:(NSString *)currentfile {
NSFileManager *filemanager = [[NSFileManager alloc] init];
if ([filemanager changeCurrentDirectoryPath: #"/"] == NO)
NSLog (#"Cannot change directory.\n");
NSString *currentpath = [filemanager currentDirectoryPath];
NSLog (#"Current directory is %#", currentpath);
[filemanager release];
NSArray*arguments = [NSArray arrayWithObjects:#"-r",currentpath,currentfile,nil];
[self touchFiles:arguments];
return;
}
- (void)touchFiles:(NSArray *)arguments {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/touch"];
[task setArguments:arguments];
[task launch];
[task release];
return;
}
You can change a file's modification time without using NSTask. Use -[NSURL setResourceValue:forKey:error:] with the key NSURLContentModificationDateKey.
As to why your use of NSTask and touch is failing, perhaps you don't have permissions to modify the file's modification time. Check the console log to see if any error was reported from touch or redirect the task's standard error output to someplace else and check that.
I have an application that checks its command line parameters and stores values in persistent stores. One of those is a password that I don't want sticking around for people to see with 'ps' and friends. The approach I'm currently looking at is to, after I've stored the values I need, relaunch the process without the command line parameters. My naive approach is this, where args[0] is the path to the application:
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:[args objectAtIndex:0]];
[task launch];
[task release];
[NSApp terminate:nil];
The child is run. However, when my app is terminated the child doesn't seem to orphan but gets stuck. Am I just way off on this one?
More info: So it seems that when I call [NSApp terminate:nil] the NSTask that was launched gets stuck, but if I just exit() then it works fine. However, I'm concerned that things that are open (keychain, plist, etc.) will be in a bad state if I do that.
And note that lots of example code out there is about some watchdog-like process that restarts a separate process when needed. I'm trying to restart the current process that's already running from within that same process.
There are plenty of examples on the web, but this one (also below) looks like it has all the code you need. There are more detailed explanations out there, as well.
// gcc -Wall -arch i386 -arch ppc -mmacosx-version-min=10.4 -Os -framework AppKit -o relaunch relaunch.m
#import <AppKit/AppKit.h>
#interface TerminationListener : NSObject
{
const char *executablePath;
pid_t parentProcessId;
}
- (void) relaunch;
#end
#implementation TerminationListener
- (id) initWithExecutablePath:(const char *)execPath parentProcessId:(pid_t)ppid
{
self = [super init];
if (self != nil) {
executablePath = execPath;
parentProcessId = ppid;
// This adds the input source required by the run loop
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(applicationDidTerminate:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
if (getppid() == 1) {
// ppid is launchd (1) => parent terminated already
[self relaunch];
}
}
return self;
}
- (void) applicationDidTerminate:(NSNotification *)notification
{
if (parentProcessId == [[[notification userInfo] valueForKey:#"NSApplicationProcessIdentifier"] intValue]) {
// parent just terminated
[self relaunch];
}
}
- (void) relaunch
{
[[NSWorkspace sharedWorkspace] launchApplication:[NSString stringWithUTF8String:executablePath]];
exit(0);
}
#end
int main (int argc, const char * argv[])
{
if (argc != 3) return EXIT_FAILURE;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[[TerminationListener alloc] initWithExecutablePath:argv[1] parentProcessId:atoi(argv[2])] autorelease];
[[NSApplication sharedApplication] run];
[pool release];
return EXIT_SUCCESS;
}
I know its a bit late to answer but this answer may help others. Here is a cool trick that can help you.
By using the terminal command, just open your application as a new instance and terminate the current instance.
This is how it is done:
....
NSString* path = [[NSBundle mainBundle] bundlePath];
NSString* cmd = [NSString stringWithFormat:#"open -n %#", path];
[self runCommand:cmd];
exit(0);
}
/// temrinal function
-(NSString*)runCommand:(NSString*)commandToRun;
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects:
#"-c" ,
[NSString stringWithFormat:#"%#", commandToRun],
nil];
NSLog(#"run command: %#",commandToRun);
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *output;
output = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
return output;
}
Create an external process that launches yours when it terminates. Then terminate. Launching Cocoa programs with NSTask doesn't work quite right.
For anyone who still wants to use NSTask to relaunch,I found one possible way: Please DO NOT set NSPipe of the NSTask,because the NSTask will terminate the app itself,once the app terminated,the NSTask that started might get stuck there.
At least for me,after I removed the NSPipe settings,my app relaunch successfully.
The following is what I do:
1. Write a command line tool which has 3 parameters: app bundle id,app full path,please note that in this command line you need to terminate the app and wait for a while to make sure it is really terminated before launch the new one,I keep checking app.isTerminated and sleep(1) if it's not terminated.
Launch the Command line tool in app using NSTask,and set the parameters accorddingly,Don't use NSPipe,simply create NSTask and launch
The app relaunches now