Detect Key Press (Non-Blocking) W/O Getc/Gets in Ruby

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:

  1. Navigate to the url.
  2. Ask if the url should be captured.
  3. 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).
  4. 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



Leave a reply



Submit