NSTask does not receive stdout outside of Xcode 4.3.3 - objective-c

this is my first post, so let me send me many thanks to all the posting guys
outside there (I use SO extensively passively - great!)
I'm working on an video exporting tool for Mac OS X using the good old Quicktime API.
Brief:
I cut frames from multiple input movies an arrange them (scaled) to a new output
movie (kind of media-kiosk).
As many of the needed QT functionality (e.g. writing timecode ...) need to be
nested in a 32-bit Application, I decided to do this offline using a 32 bit command line
tool. The tool renders frame by frame (offline) and prints the current progress
in values between 0.0 and 1.0
It is invoked by the main application (Cocoa, GUI, the modern stuff) via
NSTask. The stout is caught by a NSPipe.
I took a look at some examples and 'll give you quick overview over my code:
NSTask *task;
NSPipe *pipe;
float progress;
// prepare the offline process
//
//
-(void) prepareOfflineExport {
task = [[NSTask alloc] init];
pipe = [[NSPipe alloc] init];
[task setLaunchPath:pathToRenderer];
[task setStandardOutput:pipe];
}
// arguments are passed outside
// invoke the process
//
-(void) startOfflineExport {
progress = 0.0f;
NSArray *argv = [NSArray arrayWithObjects: /* command line args */, nil];
[task setArguments:argv];
NSFileHandle *fh = [pipe fileHandleForReading];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataReady:) name:NSFileHandleReadCompletionNotification object:fh];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskTerminated:) name:NSTaskDidTerminateNotification object:task];
[task launch];
[fh readInBackgroundAndNotify];
}
// called when data ready
//
//
-(void) dataReady:(NSNotification*)n {
NSData *d = [[n userInfo] valueForKey:NSFileHandleNotificationData];
if([d length]) {
NSString *s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
progress = [s floatValue];
}
}
// called when process exits
//
//
-(void) taskTerminated:(NSNotification*)n {
task = nil;
progress = 1.0f;
}
Now the Problem:
When launching the application inside Xcode (via "run"), everything works fine.
The Invocation is done proper, the process is visible in the activity monitor and
the NSLevelIndicator (on the guy of the innovating app) is proceeding well according
the (float) progress variable.
BUT: if i "archive" the application and execute it outside of Xcode, the stdout of my
Command Line Tool never seem to reach the application. I tried writing a debug file
in
-(void) dataReady:(NSNotification*)n
No chance, it is never called! I tested the issue on several Macs, same problem...
Did I make an obvious mistake or is there some preferences to configure (Sandboxing is off),
maybe known issues that I overlooked?
Thank you for help
Greetings
Mat

Related

Run a shell command on Objective c and simultaneously get output

Let's say I want to run curl -o http://example.com/file.zip via an Objective C app and I want to have a label or text box containing the download status which gets updated while the command is running. Maybe this could be achieved using dispatch_async, but now sure how. Before marking as duplicate, the methods I found, run the command, and after it has finished you get the output. I want to get the output while it's running, kinda like a terminal emulator.
You need to connect a NSPipe to the NSTask using the standardOutput property and register to receive Data Available notifications.
#interface TaskMonitor: NSObject
#property NSPipe *outputPipe;
#end
#implementation TaskMonitor
-(void)captureStandardOutput:(NSTask *)process {
self.outputPipe = [NSPipe new];
process.standardOutput = self.outputPipe;
//listen for data available
[self.outputPipe.fileHandleForReading waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification object:self.outputPipe.fileHandleForReading queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSData *output = self.outputPipe.fileHandleForReading.availableData;
NSString *outputString = [[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
// do something with the string chunk that has been received
NSLog(#"-> %#",outputString);
});
//listen again...
[self.outputPipe.fileHandleForReading waitForDataInBackgroundAndNotify];
}];
}
#end

How to launch Apps, from my App, with a custom parameter so I can check whether the app was launched by me?

I'm working on this app that launches other apps. I'm listening to app launches using:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(appLaunched:) name:NSWorkspaceDidLaunchApplicationNotification
object:nil];
And I launch them using (Mail is just an example):
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:#"lalalala"], NSWorkspaceLaunchConfigurationArguments, nil];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL URLWithString:#"/Applications/Mail.app"] options:NSWorkspaceLaunchWithoutActivation configuration:dict error:nil];
I did some research, and I saw that you can send an argument when you launch an app (that's why I used the var dict in the code above), but I'm having an issue with this: even using NSWorkspaceLaunchWithoutActivation, the Mail.app is launched and becomes focused with a new composing window. I don't know why it's doing that.
Another thing, if I manage to successfully send a custom argument without focusing the app, how can I check if the app was launched by me (check if the argument is there)?
PS: I'm looking for App Store-ready methods.
Send the timestamp (UTC) together with the app name you started to your server or a local file if possible.
Then you can track it.
Firstly, I'd try NSWorkspaceLaunchAndHide if NSWorkspaceLaunchWithoutActivation isn't "working". Not ideal, no.. but a kludge...
Secondly... here's a "full, running example" that does the trick..
#import <Cocoa/Cocoa.h>
NSString *psAUX(NSString*grep) {
FILE *read_f; char buff[BUFSIZ+1]; int char_rd; NSString *res, *cmnd;
memset(buff, '\0', sizeof(buff));
cmnd = [NSString stringWithFormat:#"/bin/ps aux|grep -i %#",grep];
read_f = popen(cmnd.UTF8String, "r");
if (read_f == NULL) return nil;
char_rd = fread(buff, sizeof(char), BUFSIZ, read_f);
if (!char_rd) return nil;
return res = [NSString stringWithUTF8String:buff], pclose(read_f), res;
}
int main(int argc, char *argv[]) { #autoreleasepool {
NSString* secretStr; NSURL *mailURL; NSDictionary *cfg; NSWorkspace *ws; NSApplication.sharedApplication;
secretStr = #"TAMPAX";
mailURL = [NSURL URLWithString:#"file:///Applications/Mail.app"];
cfg = #{NSWorkspaceLaunchConfigurationArguments:#[secretStr]};
ws = NSWorkspace.sharedWorkspace;
[ws launchApplicationAtURL:mailURL options:0 configuration:cfg error:nil];
fprintf(stderr,"%s",
[psAUX(#"Mail.app") containsString:secretStr]
? "You ARE Mail's baby's daddy!"
: "Hands off, she's NOT yours!");
[NSApp run]; } }
NSLog -> You ARE Mail's baby's daddy!
Congratulations!
You can create a new Task using NSTask. With NSTask you can set arguments as well as some environment variables to app so that you can check if it is launched by you or by someone else.
Here is the sample code sniffet to do so:
NSTask* taskApp = [[NSTask alloc] init];
[taskApp setLaunchPath:#"App path goes here"];
[taskApp setArguments:[NSArray arrayWithObjects:#"Arg1",#"arg2", nil]];
[taskApp setEnvironment: [[NSProcessInfo processInfo] environment]];
[taskApp launch];

NSUserScriptTask difficulties

I've been trying to make do (see this and this) with the recent NSUserScriptTask class and its subclasses and so far I've solved some problems, but some others remain to be solved. As you can see from the docs, NSUserScriptTask does not allow for the cancellation of tasks. So, I decided to create a simple executable that takes as arguments the path to the script and runs the script. That way, I can launch the helper from my main app using NSTask and call [task terminate] when necessary. However, I require:
The main app to receive output and errors from the helper it launched
The helper only terminating when the NSUserScriptTask is done
The code for the main app is simple: just launch an NSTask with the proper info. Here's what I have now (for the sake of simplicity I ignored the code for security-scoped bookmarks and the like, which are out of the problem. But don't forget this is running sandboxed):
// Create task
task = [NSTask new];
[task setLaunchPath: [[NSBundle mainBundle] pathForResource: #"ScriptHelper" ofType: #""]];
[task setArguments: [NSArray arrayWithObjects: scriptPath, nil]];
// Create error pipe
NSPipe* errorPipe = [NSPipe new];
[task setStandardError: errorPipe];
// Create output pipe
NSPipe* outputPipe = [NSPipe new];
[task setStandardOutput: outputPipe];
// Set termination handler
[task setTerminationHandler: ^(NSTask* task){
// Save output
NSFileHandle* outFile = [outputPipe fileHandleForReading];
NSString* output = [[NSString alloc] initWithData: [outFile readDataToEndOfFile] encoding: NSUTF8StringEncoding];
if ([output length]) {
[output writeToFile: outputPath atomically: NO encoding: NSUTF8StringEncoding error: nil];
}
// Log errors
NSFileHandle* errFile = [errorPipe fileHandleForReading];
NSString* error = [[NSString alloc] initWithData: [errFile readDataToEndOfFile] encoding: NSUTF8StringEncoding];
if ([error length]) {
[error writeToFile: errorPath atomically: NO encoding: NSUTF8StringEncoding error: nil];
}
// Do some other stuff after the script finished running <-- IMPORTANT!
}];
// Start task
[task launch];
Remember, I need the termination handler to only run when: (a) the task was cancelled (b) the task terminated on its own because the script finished running.
Now, on the helper side things start to get hairy, at least for me. Let's imagine for the sake of simplicity that the script is an AppleScript file (so I use the NSUserAppleScriptTask subclass - on the real world I'd have to accomodate for the three types of tasks). Here's what I got so far:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSString* filePath = [NSString stringWithUTF8String: argv[1]];
__block BOOL done = NO;
NSError* error;
NSUserAppleScriptTask* task = [[NSUserAppleScriptTask alloc] initWithURL: [NSURL fileURLWithPath: filePath] error: &error];
NSLog(#"Task: %#", task); // Prints: "Task: <NSUserAppleScriptTask: 0x1043001f0>" Everything OK
if (error) {
NSLog(#"Error creating task: %#", error); // This is not printed
return 0;
}
NSLog(#"Starting task");
[task executeWithAppleEvent: nil completionHandler: ^(NSAppleEventDescriptor *result, NSError *error) {
NSLog(#"Finished task");
if (error) {
NSLog(#"Error running task: %#", error);
}
done = YES;
}];
// Wait until (done == YES). How??
}
return 0;
}
Now, I have three questions (which are the ones I want to ask with this SO entry). Firstly, "Finished task" never gets printed (the block never gets called) because the task never even starts executing. Instead, I get this on my console:
MessageTracer: msgtracer_vlog_with_keys:377: odd number of keys (domain: com.apple.automation.nsuserscripttask_run, last key: com.apple.message.signature)
I tried running the exact same code from the main app and it completes without a fuss (but from the main app I lose the ability to cancel the script).
Secondly, I only want to reach the end of main (return 0;) after the completion handler is called. But I have no idea how to do that.
Thridly, whenever there's an error or output from the helper I want to send that error/output back to the app, which will receive them through the errorPipe/outputPipe. Something like fprintf(stderr/stdout, "string") does the trick, but I'm not sure if it is the right way to do it.
So, in short, any help regarding the first and second problems is appreciated. The third one I just want to make sure that's how I'm supposed to do it.
Thanks
Question 1: The sub-task doesn't run because its parent exits immediately. (The log message about "odd number of keys" is a bug in NSUserScriptTask, and happens because your helper doesn't have a bundle identifier, but is otherwise harmless and irrelevant to your problem.) It exits immediately because it's not waiting for the completion block to fire, which brings us to...
Question 2: How do you wait for an asynchronous completion block? This has been answered elsewhere, including Wait until multiple networking requests have all executed - including their completion blocks, but to recap, use dispatch groups, something like this:
dispatch_group_t g = dispatch_group_create();
dispatch_group_enter(g);
[task executeWithAppleEvent:nil completionHandler:^(NSAppleEventDescriptor *result, NSError *e) {
...
dispatch_group_leave(g);
}];
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
dispatch_release(g);
This same pattern works for any call that has a completion block you want to wait for. If you wanted another notification when the group finishes instead of waiting for it, use dispatch_group_notify instead of dispatch_group_wait.
As a side note, the way you’re testing error after allocating the NSUserAppleScriptTask is incorrect. The value of error is defined if and only if the function result is nil (or NO, or whatever indicates failure). If the function succeeds (which you know if it returns non-nil), then error may be anything -- the function may set it to nil, it may leave it undefined, it may even fill it in with a real object. (See also What's the Point of (NSError**)error?)

InterProcess Communication on MacOSX Lion

I'm trying to figure out how to set up IPC between my custom app and a pre-made program.
I'm using MacOSX Lion 10.7.2 and Xcode 4.2.1.
It doesn't matter actually what program exactly, since I believe that a similar reasoning may be applied to any kind of external process.
For testing purposes I'm using a simple bash script:
#test.sh
echo "Starting"
while read out
do
echo $out
done
What I would like to achieve is to redirect input and output of this script, using my app to send inputs to it and read its outputs.
I tried to use NSTask,NSPipe and NSFileHandle as follows:
-(void)awakeFromNib {
task = [[NSTask alloc] init];
readPipe = [NSPipe pipe];
writePipe = [NSPipe pipe];
[task setStandardOutput:readPipe];
[task setStandardInput:writePipe];
[task setLaunchPath:#"/path/test.sh"];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(read:)
name:NSFileHandleReadCompletionNotification
object:nil];
[[readPipe fileHandleForReading] readInBackgroundAndNotify];
[task launch];
}
-(IBAction)write:(id)sender {
NSLog(#"Write called: %d %#\n",[task isRunning],writePipe);
NSFileHandle *writeHandle = [writePipe fileHandleForWriting];
NSString *message = #"someString";
[writeHandle writeData:[message dataUsingEncoding:NSUTF8StringEncoding] ];
}
-(void)read:(NSNotification*)notification {
NSString *output = [[NSString alloc] initWithData:[[notification userInfo] valueForKey: NSFileHandleNotificationDataItem]
encoding:NSUTF8StringEncoding];
NSLog(#"%#",output);
[output release];
[[notification object] readInBackgroundAndNotify];
}
but I'm able only to read the output of test.sh, not to send it any input.
Actually any other example I saw on the web is pretty similar to my code, so I'm not sure if this issue is due to some mistake(s) of mine or to other issues (like app's sandboxing of MacOS Lion).
I've checked XPC documentation, but, according to my researches, in order to use XPC API to IPC, both sides should connect to the same service.
That's not what I'm looking for since I don't want to alter the script in any way, I just want redirect its input and output.
Is my issue due to the lack of XPC and/or to app's sandboxing?
If yes, is there a way to use XPC without modifying the script?
If no, then may somebody explain me what I'm doing wrong?
You don't need XPC for this. Won't make any difference.
Is your script / external process able to read input when you pipe something to it on the command line
% echo "foobar" | /path/test.sh
?
How much data are you sending to it. Writing will be buffered. IIRC -synchronizeFile will flush buffers -- same as fsync(2).

Write to NSTasks standard input after launch

I am currently trying to wrap my head around the hole NSTask, NSPipe, NSFileHandle business. So I thought I write a little tool, which can compile and run C code. I also wanted to be able to redirect my stdout and stdin to a text view.
Here is what I got so far.
I used code from this post to redirect my stdio: What is the best way to redirect stdout to NSTextView in Cocoa?
NSPipe *inputPipe = [NSPipe pipe];
// redirect stdin to input pipe file handle
dup2([[inputPipe fileHandleForReading] fileDescriptor], STDIN_FILENO);
// curInputHandle is an instance variable of type NSFileHandle
curInputHandle = [inputPipe fileHandleForWriting];
NSPipe *outputPipe = [NSPipe pipe];
NSFileHandle *readHandle = [outputPipe fileHandleForReading];
[readHandle waitForDataInBackgroundAndNotify];
// redirect stdout to output pipe file handle
dup2([[outputPipe fileHandleForWriting] fileDescriptor], STDOUT_FILENO);
// Instead of writing to curInputHandle here I would like to do it later
// when my C program hits a scanf
[curInputHandle writeData:[#"123" dataUsingEncoding:NSUTF8StringEncoding]];
NSTask *runTask = [[[NSTask alloc] init] autorelease];
[runTask setLaunchPath:target]; // target was declared earlier
[runTask setArguments:[NSArray array]];
[runTask launch];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(stdoutDataAvailable:) name:NSFileHandleReadCompletionNotification object:readHandle];
And here the stdoutDataAvailable method
- (void)stdoutDataAvailable:(NSNotification *)notification
{
NSFileHandle *handle = (NSFileHandle *)[notification object];
NSString *str = [[NSString alloc] initWithData:[handle availableData] encoding:NSUTF8StringEncoding];
[handle waitForDataInBackgroundAndNotify];
// consoleView is an NSTextView
[self.consoleView setString:[[self.consoleView string] stringByAppendingFormat:#"Output:\n%#", str]];
}
This Program is working just fine. It is running the C program printing the stdout to my text view and reading "123" from my inputPipe. Like indicated in my comment above I would like to provide the input while the task is running once it is needed.
So there are two questions now.
Is there a way to get a notification as soon as somebody tries to read data from my inputPipe?
If the answer to 1 is no, is there a different approach I can try? Maybe using a class other than NSTask?
Any help, sample code, links to other resources are highly appreciated!
I'm not sure whether you can detect a "pull" on an NSPipe. I do have a vague sense that polling for write-availability with select() or using kqueue to look for I/O availability events on the underlying file descriptor of your NSFileHandle might do the trick, but I'm not very familiar with using those facilities in this way.
Do you have to support arbitrary C programs, or is it a special daemon or something you've developed?
If it's your own program, you could watch for requests for feedback on outputPipe, or just blast input onto the inputPipe as you find out what it is you want to send, and let the C program consume it when it's ready; if it's somebody else's code, you may be able to hook scanf and friends using a link-time method (since it's code you're compiling) like the one described in Appendix A-4 of:
http://www.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf
The gist of it is to make a .dylib with your custom I/O functions (which may send some sigil to your app indicating that they need input), link that into the built program, set an environment variable (DYLD_BIND_AT_LAUNCH=YES) for the launched task, and run it. Once you've got those hooks in, you can provide whatever conveniences you want for your host program.