Simulating keystrokes for automated user input

R

Ryan Mohr

I've written a simple console app (type command. get results. repeat.)
and I'm looking for a way to send it simulated keystrokes (hoping to
remotely send commands through a socket connection and have them "typed"
in the main console). I thought the solution would be easy but I'm
finding it much harder than anticipated.

The solution needs to generate a keystroke programmatically such that
the console has no idea whether it was physically typed by the user or
not.

Tried writing to a modified stdin, directly editing /dev/tty, redirected
echos... no luck yet.

Any ideas?
 
J

Joel VanderWerf

Ryan said:
I've written a simple console app (type command. get results. repeat.)
and I'm looking for a way to send it simulated keystrokes (hoping to
remotely send commands through a socket connection and have them "typed"
in the main console). I thought the solution would be easy but I'm
finding it much harder than anticipated.

The solution needs to generate a keystroke programmatically such that
the console has no idea whether it was physically typed by the user or
not.

Tried writing to a modified stdin, directly editing /dev/tty, redirected
echos... no luck yet.

What about ruby's PTY lib?

PTY.spawn 'your-app' do |r,w,cid| ... end
 
B

brabuhr

I've written a simple console app (type command. get results. repeat.)
and I'm looking for a way to send it simulated keystrokes (hoping to
remotely send commands through a socket connection and have them "typed"
in the main console). =A0I thought the solution would be easy but I'm
finding it much harder than anticipated.

The solution needs to generate a keystroke programmatically such that
the console has no idea whether it was physically typed by the user or
not.

http://programming.ccp14.ac.uk/expect/
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/66884

http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Robot.html
http://www.devdaily.com/blog/post/ruby/ruby-jruby-program-that-runs-java-ro=
bot-class

http://www.semicomplete.com/projects/xdotool/
 
B

Brian Candler

Ryan said:
The solution needs to generate a keystroke programmatically such that
the console has no idea whether it was physically typed by the user or
not.

Tried writing to a modified stdin, directly editing /dev/tty, redirected
echos... no luck yet.

Any ideas?

Spawn a pty. There's pty.so in the standard library, which is a C
extension and unfortunately not well documented, but google for
"PTY.spawn" to find some examples, e.g.

http://www.ruby-forum.com/topic/133560

http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/branches/ruby_1_8/ext/pty/expect_sample.rb?view=markup
 
R

Ryan Mohr

Brian said:
Spawn a pty. There's pty.so in the standard library, which is a C
extension and unfortunately not well documented, but google for
"PTY.spawn" to find some examples, e.g.

Thanks for the suggestion Brian but I don't think that will work for my
case. I need to send keystrokes to stdin of the current process itself.
Maybe there's a way to do that with PTY but I couldn't get it to work.

Here's a quick example that demonstrates the issue:

Code:
# prompt is already up and waiting
t = Thread.new {
print "Enter command: "
$stdout.flush

# get the next command to run from the user
command = gets

puts "Executing command #{command}"
}

# want this to enter the desired command as though it
#  was typed by the user himself (show up in stdout but
#  also be picked up by any open 'gets' requests)

# ... ? (send "foo" to stdin of the current process)


t.join

puts "Done."
 
B

Brian Candler

Ryan said:
Thanks for the suggestion Brian but I don't think that will work for my
case. I need to send keystrokes to stdin of the current process itself.

Then I think you're stuck. When your ruby process was started, its stdin
file descriptor was connected to something (e.g. a terminal or a pipe).
Only that thing can generate data for your app to read; e.g. you can't
write to the read end of a pipe.

You could connect forcibly reopen stdin on a different fd:

rd, wr = IO.pipe
STDIN.reopen(rd)
Thread.new do
wr.puts "hello"
end
line = gets
puts line.inspect

That's really horrible though. It would be better to fork your program
entirely:

IO.popen("-","rb+") do |pipe|
if pipe
# parent
pipe.puts "hello"
res = pipe.gets
puts "got: #{res}"
else
# child
line = gets
puts line.inspect
end
end

If what you're trying to accomplish is unit testing of a CLI-driven
program, then I'd suggest either running the program under test as a
child (e.g. using IO.popen or PTY.spawn), or factor out your objects so
that they can read and write to arbitrary IO objects that you pass in,
so that you can pass them a StringIO or one end of a pipe.

Otherwise, perhaps you can explain what you are trying to achieve?

Regards,

Brian.
 
R

Ryan Mohr

Brian Candler wrote
Otherwise, perhaps you can explain what you are trying to achieve?

In addition to the CLI, I'm developing a simple gui interface. As the
user interacts with the gui, commands are sent back to the CLI through a
socket connection.

At this point my CLI has nearly 100 commands and growing, so the easiest
approach would be for the GUI to just send the text command desired and
have it "typed" into the main app.

If I go the internal API route (eg. no simulated typing), the command
executes and the output is displayed, but now the old prompt is buried
above the new output and the prior 'gets' request is still waiting for
input. Since some of the commands have nested commands requesting input
of their own, this stale 'gets' call is problematic.

It's all pretty messy and probably hard to understand from my
description. The addition of the gui was an afterthought so the CLI
wasn't designed for this kind of use. The elegance of a simulated
typing solution has pushed me towards using applescript to solve the
issue (but even that has issues of its own such as the inability to send
text to specific background windows).

Thanks for all the help though. I'll keep hacking away at it.
 
B

brabuhr

Brian Candler wrote

In addition to the CLI, I'm developing a simple gui interface. =A0As the
user interacts with the gui, commands are sent back to the CLI through a
socket connection.

At this point my CLI has nearly 100 commands and growing, so the easiest
approach would be for the GUI to just send the text command desired and
have it "typed" into the main app.

In the GUI program run the CLI via PTY.spawn and let the GUI program
write ("type") commands (directly) to the CLI (instead of using a
socket). If the GUI and CLI must run on separate hosts, launch the
CLI remotely via, for example, Net::SSH. As a third alternative write
a small helper program to run on the server, it will run the actual
CLI via PTY.spawn and listen on the socket for connections from the
GUI.
 
J

Jesse Jurman

I don't exactly understand what your trying to make, but in any
instance, whether it's gui or not, you might have a program turn your
input into a file, and then have your original program check if such a
file exists, read it, and then delete it... I haven't tried something
like it yet, but I plan on using a technique like it in one of my own
programs.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,533
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top