How to control external console programs

As you know, there are many Unix programs which just calls external program and controls the external program. Usually such programs are GUI front-end to the external programs.

Such programs work like this.

  1. Launch an external program as a task. e.g. using NSTask if you write for Mac
  2. connect to the external program using pipes, one for input and another for output

Feels like that GUI codes and the external program are not integrated tightly, right?
However, there are a lot of Unix programs runs like this mechanism. Sometimes there can be delay when a button on the GUI is clicked to command the external program do something. This is how MPlayer OS X and MPlayer OS X Extended work. An opposite case can be the VLC, then. VLC codes contains all the codes for its functionality by having its own codes or is linked to external library like libavformat, libavcodec, libx264 and so on.

Let’s take a look at this part of codes in MPlayer OS X Extended.

// initialize  mplayer task object
myMplayerTask=[[NSTask alloc] init];

// create standard input and output for application
[myMplayerTask setStandardInput: [NSPipe pipe]];
[myMplayerTask setStandardOutput: [NSPipe pipe]];
[myMplayerTask setStandardError: [NSPipe pipe]];

// add observer for termination of mplayer
[[NSNotificationCenter defaultCenter] addObserver: self
		selector: @selector(mplayerTerminated) 
		name:NSTaskDidTerminateNotification
		object:myMplayerTask];
// add observer for available data at mplayers output 
[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(readOutputC:)
		name:NSFileHandleReadCompletionNotification
		object:[[myMplayerTask standardOutput] fileHandleForReading]];
[[NSNotificationCenter defaultCenter] addObserver:self
		selector:@selector(readError:)
		name:NSFileHandleReadCompletionNotification
		object:[[myMplayerTask standardError] fileHandleForReading]];

// set launch path and params
if (force32bitBinary) {
	[myMplayerTask setLaunchPath:@"/usr/bin/arch"];
	[aParams insertObject:@"-i386" atIndex:0];
	[aParams insertObject:myPathToPlayer atIndex:1];
} else
	[myMplayerTask setLaunchPath:myPathToPlayer];

// set launch arguments
[myMplayerTask setArguments:aParams];

...

// activate notification for available data at output
[[[myMplayerTask standardOutput] fileHandleForReading]
		readInBackgroundAndNotifyForModes:parseRunLoopModes];
[[[myMplayerTask standardError] fileHandleForReading]
		readInBackgroundAndNotifyForModes:parseRunLoopModes];

Setting notification is a kind of helper to make status checking easier.

For programs where it doesn’t allow interaction on console, it will not be possible to control the external program using an input pipe. Writing some text to an input pipe is like to typing a command, which is same to the text, on the external program screen.

So, programs like MPlayer OS X Extended handles “stop” such way.
Let’s check it.

- (void) stop
{
	if (isRunning) {
		if (!(stateMask & MIStateStoppedMask))
			[self sendCommand:@"quit"];
		[myMplayerTask waitUntilExit];
	}
}

- (void)sendCommand:(NSString *)aCommand
{
	[self sendCommand:aCommand withOSD:MISurpressCommandOutputConditionally andPausing:MICommandPausingKeep];
}

- (void)sendCommand:(NSString *)aCommand withOSD:(uint)osdMode andPausing:(uint)pausing
{
	[self sendCommands:[NSArray arrayWithObject:aCommand] withOSD:osdMode andPausing:pausing];
}

- (void)sendCommands:(NSArray *)aCommands withOSD:(uint)osdMode andPausing:(uint)pausing
{	
    ....

	int i;
	for (i=0; i < [aCommands count]; i++) {

		[self sendToMplayersInput:[NSString stringWithFormat:@"%@%@\n",pausingPrefix,[aCommands objectAtIndex:i]]];

	}
		
    ...
}

- (void)sendToMplayersInput:(NSString *)aCommand
{
    if (myMplayerTask) {
		if ([myMplayerTask isRunning]) {
			@try {
				NSFileHandle *thePipe = [[myMplayerTask standardInput] fileHandleForWriting];
				[thePipe writeData:[aCommand dataUsingEncoding:NSUTF8StringEncoding]];
			}
			@catch (NSException * e) {

				if ([myMplayerTask isRunning])
					[myMplayerTask terminate];
			}
		}
	}
}

We can confirm that it writes a textual command through the input pipe in sendToMplayersInput message.

So, if you feel like that it is not right way to control external programs like this, just take it easy. Don’t feel guilty and just do it. :)

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: