korpenkraxar has asked for the wisdom of the Perl Monks concerning the following question:

Hi! I've been surfing around for hours to find an answer to this problem, but feel I have to resort to consult the local collective perl/UNIX wisdom. I am writing a multimedia remote control program and am looking for a way to send input to an interactive CLI program (the "splay" executable in "Helix DNA client"), which reads input from STDIN to for instance play/pause a music file. I would like to add a remote control interface by "simulating" local STDIN input. I know how to use pipes in system calls for processes that are executed from within a perl program; the catch in this scenario is that the target program is a completely external process started by the user before my perl program is started. I am using a GNU/Linux system and the closest I've come to a solution is to simulate key inputs using the xautomation program. However, this program needs the target window to be in focus to work. I am looking for a solution along the lines of "send STDIN input to PID". Any ideas? Is this at all possible?
  • Comment on Sending STDIN input to external program

Replies are listed 'Best First'.
Re: Sending STDIN input to external program
by plobsing (Friar) on Dec 31, 2007 at 05:59 UTC
    As I see it, you have 2 options here:
    1. Have your script pretend to be user interaction
    2. Control the STDIN filehandle

    Your use of xautomation is an example of the first. There are other options in this category, however, I find these kinds of solutions to be kludgey in general.

    Otherwise you have to control the STDIN filehandle. I know of no way of stealing the standard input of a process away from its initial input (although it is within the realm of the plausible). This leaves you with needing to open splay from Perl.

    However, you can be pretty subtle about this if you are careful. Create an splay proxy program that mixes lines read from a named pipe (which you could map to the PID of the splay instance) into the STDIN. You could even replace your real splay with the proxy, moving the real thing to splay-bin or something to that effect. Now, if you want to insert lines into your splays STDIN, all you have to do is write to the named pipe and your proxy handles things nicely.
Re: Sending STDIN input to external program
by graff (Chancellor) on Dec 31, 2007 at 07:38 UTC
    It depends on a few things...

    First: would "splay" be running as a child process of the controlling perl script? (If it's being run independently from a separate shell, I think the idea of controlling it from a perl script won't make any sense. In order for the perl script to control the process by "talking" to splay's STDIN, splay has to be a child process launched by the perl script.

    Second: does splay provide any feedback via its STDOUT, such that a later entry to its STDIN (the next command) would depend on what the feedback was? In other words, when you use splay in the "normal way", do the inputs you enter depend on the stuff it prints to the terminal between your entries?

    If you are talking about controlling a child process where your commands don't depend on feedback, you can open the child process as an output file handle, like this:

    open( my $splay, "|-", "splay @args" ) or die "Couldn't start splay: $ +!"; # Now just print command line strings to the $splay file handle... sleep 10; print $splay "STOP\n"; # (just guessing what sort of command you wou +ld send)
    On the other hand, if you need to base some of your commands on information that splay is printing to its STDOUT, you'll need to look into the Expect module -- it's designed specifically for that kind of thing, and gives you the ability to handle some fairly intricate and varied interactions based on "dialogs" with a child process.

    Update: Sorry, after reading the OP more carefully, I see you clearly said that "splay" is not being run as a child process of the perl script. I think there should be a way to create a "wrapper" script for starting "splay" (you could rename the actual splay executable to, say, "splay.real", and have the wrapper script named "splay").

    The wrapper would provide some sort of IPC mechanism (named pipe, sockets or whatever -- see perlipc) so that some other script, being run independently, could talk to it and send commands that would be relayed to the "splay.real" child process. In this case, the wrapper script is running splay as a child process, using a pipeline file handle or Expect, as appropriate, to control it based on the inputs it gets from a connecting perl script.

    But here's the catch: if a user is launching splay before the control perl script starts up, is the user expecting to send manual commands to splay via its STDIN? I don't think you can have it both ways -- either its controlled interactively by a user typing to the tty (normal stdin operation), or it's getting stdin from a parent process.

    (Last update: on second thought, the wrapper script could be written in a clever way that would support both "normal" tty input and "intrusive" input from an independent client process. It simply has to poll both sources for as long as the splay.real child is running. Could be an interesting app on a multi-user system, if two or more people have permission to control the same process simultaneously...)

Re: Sending STDIN input to external program
by zentara (Cardinal) on Dec 31, 2007 at 14:47 UTC
    If you can run under X11, you "might" find a way with X11::SendEvent.

    The last time I tried it, this was as far as I got...... a non-working snippet

    #!/usr/bin/perl use warnings; use strict; use X11::SendEvent; use X11::WMCtrl; use Data::Dumper; my $wmctrl = X11::WMCtrl->new; my @windows = $wmctrl->get_windows; print Dumper([\@windows]),"\n"; $wmctrl->activate('foo'); $|++; my $win = X11::SendEvent->new( win => [ 'foo' ], debug => 1 ); for(1..100){ $win->SendString( "testing", [ "Return" ] ); $win->SendString( "user", [ "Return" ], "joe", [ "9/1" ] ); $win->SendKeycode( 119 ); $win->SendString( "zentara", [ "Return" ], [ "Return" ] ); } <>;

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum