views:

370

answers:

3

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];
+4  A: 

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.

Peter Hosey
I edited the question with a non-working implementation? Any clue?
Sney
Snej: You have the arguments to `dup2` the wrong way around. Then you pass an NSFileHandle pointer as a file descriptor to create an NSFileHandle. Then you pretend the write end is a socket and try to accept a connection, even though neither FD was ever returned from listen(2) (because they're a pipe, not sockets). Fix the `dup2` call, don't pretend the file handle pointer is a file descriptor, don't try to create another file handle for either of the ends, and try to *read* from *the read end*.
Peter Hosey
+2  A: 

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 
elessar
A: 

Have a look at PseudoTTY.app!

chad