5

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];
zoul
  • 102,279
  • 44
  • 260
  • 354
Sney
  • 2,486
  • 4
  • 32
  • 48
  • what does python has to do with this? – Eimantas Mar 09 '10 at 08:52
  • @Eimantas: You're right, it is not directly related to the question. Actually I'm using PyObj to communicate with Python code writing to **stdout** which I want to display. I should remove the tag... – Sney Mar 09 '10 at 09:12

4 Answers4

12

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
  • 136
  • 3
  • This helped me a lot. Thanks! I used your suggestion and added these two messages to make the textView grow and autoscroll using the following:[myTextView setText:[myTextView.text stringByAppendingString:str]]; [myTextView scrollRangeToVisible:NSMakeRange([myTextView.text length], 0)]; – ThinkBonobo Apr 06 '14 at 21:35
  • successful `dup2` should be followed by `[[pipe fileHandleForWriting] closeFile];` to cleanup – Leonid Usov Aug 04 '18 at 18:00
5

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
  • 95,783
  • 15
  • 211
  • 370
  • I edited the question with a non-working implementation? Any clue? – Sney Mar 09 '10 at 08:48
  • 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 Mar 09 '10 at 18:32
2

Have a look at PseudoTTY.app!

chad
  • 21
  • 1
0

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] ;

}
Randel S
  • 362
  • 4
  • 9