Hi: I want to redirect stdout to a NSTextView. Could this also work with outputs of subprocesses? What might be the best way to achieve this?
EDIT:
According to Peter Hosey answer I implemented the following. But I do not get a notification. What am I doing wrong?
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *pipeHandle = [pipe fileHandleForWriting];
dup2(STDOUT_FILENO, [pipeHandle fileDescriptor]);
NSFileHandle *fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:pipeHandle];
[fileHandle acceptConnectionInBackgroundAndNotify];
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:@selector(handleNotification:) name:NSFileHandleConnectionAcceptedNotification object:fileHandle];
I was looking to do the same thing and came across this post. I was able to figure it out after reading this and Apple's documentation. I'm including my solution here. "pipe" and "pipeReadHandle" are declared in the interface. In the init method I included the following code:
pipe = [NSPipe pipe] ;
pipeReadHandle = [pipe fileHandleForReading] ;
dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stdout)) ;
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(handleNotification:) name: NSFileHandleReadCompletionNotification object: pipeReadHandle] ;
[pipeReadHandle readInBackgroundAndNotify] ;
The handleNotification: method is
[pipeReadHandle readInBackgroundAndNotify] ;
NSString *str = [[NSString alloc] initWithData: [[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] encoding: NSASCIIStringEncoding] ;
// Do whatever you want with str
I want to redirect stdout to a NSTextView.
Your own stdout?
Could this also work with outputs of subprocesses?
Sure.
What might be the best way to achieve this?
File descriptors are your friend here.
Create a pipe (using either NSPipe or pipe(2)) and dup2 its write end onto STDOUT_FILENO
. When invoking subprocesses, don't set their stdout; they'll inherit your stdout, which is your pipe. (You may want to close the read end in the subprocess, though. I'm not sure whether this will be necessary; try it and find out. If it does turn out to be, you'll need to use fork
and exec
, and close the read end in between.)
Read from the read end of the pipe in the background asynchronously, using either kevent
or NSFileHandle. When new data comes in, interpret it using some encoding and append it to the contents of the text view.
If the text view is in a scroll view, you should check the scroll position before appending to it. If it was at the end, you'll probably want to jump it back to the end after appending.
Have a look at PseudoTTY.app!
For iOS use stderr instead of stdout.
NSPipe *pipe = [NSPipe pipe];
pipeHandle = [pipe fileHandleForReading];
dup2([[pipe fileHandleForWriting] fileDescriptor], fileno(stderr));
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(handleNotification:) name: NSFileHandleReadCompletionNotification object: pipeHandle] ;
[pipeHandle readInBackgroundAndNotify] ;
-(void)handleNotification:(NSNotification *)notification{
[pipeHandle readInBackgroundAndNotify] ;
NSString *str = [[NSString alloc] initWithData: [[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] encoding: NSASCIIStringEncoding] ;
}