How can I check for keyboard input without stopping execution?
From what I could find and some hacking around, I managed to put together something that will immediately echo the keys you press when running in command line.
require 'io/console'
loop do
p STDIN.getch
end
But as the referenced answer mentions, you'll want to capture SIGTERMs so you don't get trapped in the program: Signal.trap("INT") { exit }
So the meat of your program and all of its processing lives in that main loop, and each go around of that loop it will grab a character from the STDIN.
Reading key pressing from a keyboard in Ruby
use the green_shoes gem or simply use red shoes, here a green_shoes working sample
['green_shoes'].each(&method(:require))
Shoes.app do
e = edit_line
info = para "NO KEY is PRESSED."
keypress do |k|
info.replace "#{k.inspect} was PRESSED."
print k
end
end
Works on any OS unlike the sollution from Detect key press (non-blocking) w/o getc/gets in Ruby
Put your shoes on !
Ruby Win32Api get single character non-blocking
If you're looking to checkup on a handful of specific keys, GetAsyncKeyState()
can be used to poll for state. Otherwise, you should implement a message loop and handle WM_CHAR
messages in your application.
You can access raw win32 functions with a library similar to the following if your environment doesn't already support the win32 functions you need.
RAA - win32-api @ raa.ruby-lang.org
Here are relevant links for GetAsyncKeyState()
.
GetAsyncKeyState() @ MSDN
Virtual-Key Codes @ MSDN (used as values for GetAsyncKeyState and other functions)
If you decide to go with a message loop, you'll need to implement it on the same thread that hosts the window for your application. A short explanation is available here:
Message Loop in Microsoft Windows @ Wikipedia
As shown at that link, there isn't a whole lot to a message loop. Your framework may already have one running behind the scenes to host the window containing your graphics. If this is the case, you don't need a 2nd thread for input; but you will need to intercept and handle windows messages for that host window.
If you have to implement a message loop on your own, you can use the skeleton at the wikipedia link. The DispatchMessage
call will direct a windows message to the appropriate window handler. You will need to look for a matching stub or handler for windows messages in your ruby framework, and handle WM_CHAR
from there.
DispatchMessage @ MSDN
WM_CHAR @ MSDN
If you wish to know when keys are pressed/depressed, then you will want to handle WM_KEYUP and WM_KEYDOWN messages.
WM_KEYUP @ MSDN
WM_KEYDOWN @ MSDN
Also note, GetAsyncKeyState()
can be called and returns key state even while another application is in the foreground. Handle WM_ACTIVATE
and WM_SETFOCUS
/WM_KILLFOCUS
messages so that your application ignores or defers checks while a different window is active if you only care about key state while your window is the primary foreground window.
WM_ACTIVATE @ MSDN
WM_SETFOCUS @ MSDN
WM_KILLFOCUS @ MSDN
ruby webdriver: how to catch key pressed while the script is running?
It looks like your problem might be solvable with STDIN.getch.
If you create a file with the following script and then run it in a command prompt (eg "ruby script.rb"), the script will:
- Navigate to the url.
- Ask if the url should be captured.
- If the user does not input anything in 10 seconds, it will proceed onto the next url. I changed it from 3 since it was too fast. You can change the time back to 3 seconds in the line
Timeout::timeout(10)
. - If the user did input something, it will save the url if the input was a space. Otherwise it will ignore it and move on.
Script:
require "watir-webdriver"
require 'io/console'
require 'timeout'
urls = ['www.google.ca', 'www.yahoo.ca', 'www.gmail.com']
saved = []
b = Watir::Browser.new
urls.each do |url|
b.goto url
# Give user 10 seconds to provide input
puts "Capture url '#{url}'?"
$stdout.flush
input = Timeout::timeout(10) {
input = STDIN.getch
} rescue ''
# If the user's input is a space, save the url
if input == ' '
saved << b.url
end
end
p saved
A couple of notes:
- Note that the inputs need to be to the command prompt rather than the browser.
- If the user presses a key before the allotted timeout, the script will immediately proceed to the next url.
How to get a single character without pressing enter?
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/2999
#!/usr/bin/ruby
begin
system("stty raw -echo")
str = STDIN.getc
ensure
system("stty -raw echo")
end
p str.chr
(Tested on my OS X system, may not be portable to all Ruby platforms). See http://www.rubyquiz.com/quiz5.html for some additional suggestions, including for Windows.
C non-blocking keyboard input
As already stated, you can use sigaction
to trap ctrl-c, or select
to trap any standard input.
Note however that with the latter method you also need to set the TTY so that it's in character-at-a-time rather than line-at-a-time mode. The latter is the default - if you type in a line of text it doesn't get sent to the running program's stdin until you press enter.
You'd need to use the tcsetattr()
function to turn off ICANON mode, and probably also disable ECHO too. From memory, you also have to set the terminal back into ICANON mode when the program exits!
Just for completeness, here's some code I've just knocked up (nb: no error checking!) which sets up a Unix TTY and emulates the DOS <conio.h>
functions kbhit()
and getch()
:
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>
struct termios orig_termios;
void reset_terminal_mode()
{
tcsetattr(0, TCSANOW, &orig_termios);
}
void set_conio_terminal_mode()
{
struct termios new_termios;
/* take two copies - one for now, one for later */
tcgetattr(0, &orig_termios);
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
/* register cleanup handler, and set the new terminal mode */
atexit(reset_terminal_mode);
cfmakeraw(&new_termios);
tcsetattr(0, TCSANOW, &new_termios);
}
int kbhit()
{
struct timeval tv = { 0L, 0L };
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
return select(1, &fds, NULL, NULL, &tv) > 0;
}
int getch()
{
int r;
unsigned char c;
if ((r = read(0, &c, sizeof(c))) < 0) {
return r;
} else {
return c;
}
}
int main(int argc, char *argv[])
{
set_conio_terminal_mode();
while (!kbhit()) {
/* do some work */
}
(void)getch(); /* consume the character */
}
Related Topics
How to Make a Post Request with Open-Uri
Get Sidekiq to Execute a Job Immediately
Rails Unable to Convert Unpermitted Parameters to Hash
How to Add to a Serialized Array
Why Is Rake Not Able to Invoke Multiple Tasks Consecutively
Notices for Sequence After Running Migration in Rails on Postgresql Application
Combine Array of Array into All Possible Combinations, Forward Only, in Ruby
Create Custom HTML Helpers in Ruby on Rails
Undefined Method with "_Path" While Using Rails Form_For
What Does "Shadowing" Mean in Ruby
Rails 3.1 Absolute Url to an Image
Returning Data from Forked Processes
How to Solve Insecure World Writable Dir /Usr in Path,Mode 040777 Warning on Ruby