Getting Input One Character at a Time

Problem

You e writing an interactive application or a terminal-based game. You want to read a users input from standard input a single character at a time.

Solution

Most Ruby installations on Unix come with the the Curses extension installed. If Curses has the features you want to write the rest of your program, the simplest solution is to use it.

This simple Curses program echoes every key you type to the top-left corner of the screen. It stops when you hit the escape key (e).[1]

[1] This code will also work in irb, but itll look strange because Curses will be fighting with irb for control of the screen.

#!/usr/bin/ruby -w # curses_single_char_input.rb require curses include Curses # Setup: create a curses screen that doesn echo its input. init_screen noecho # Cleanup: restore the terminal settings when the program is exited or # killed. trap(0) { echo } while (c = getch) != ?e do setpos(0,0) addstr("You typed #{c.chr.inspect}") end

If you don want Curses to take over your program, you can use the HighLine library instead (available as the highline gem). It does its best to define a get_ character method that will work on your system. The get_ character method itself is private, but you can access it from within a call to ask:

require ubygems require highline/import while (c = ask(\) { |q| q.character = true; q.echo = false }) != "e" do print "You typed #{c.inspect}" end

Be careful; ask echoes a newline after every character it receives.[2] Thats why I use a print statement in that example instead of puts.

[2] This actually happens at the end of HighLine.get_response, which is called by ask.

Of course, you can avoid this annoyance by hacking the HighLine class to make get_character public:

class HighLine public :get_character end input = HighLine.new while (c = input.get_ character) != ?e do puts "You typed #{c.chr.inspect}" end

Discussion

This is a huge and complicated problem that (fortunately) is completely hidden by Curses and HighLine. Heres the problem: Unix systems know how to talk to a lot of historic and modern terminals. Each one has a different feature set and a different command language. HighLine (through the Termios library it uses on Unix) and Curses hide this complexity.

Windows doesn have to deal with a lot of terminal types, but Windows programs don usually read from standard input either (much less one character at a time). To do single- character input on Windows, HighLine makes raw Windows API calls. Heres some code based on HighLines, which you can use on Windows if you don want to require HighLine:

require Win32API def getch @getch ||= Win32API.new(crtdll, \_getch, [], L) @getch.call end while (c = getch) != ?e puts "You typed #{c.chr.inspect}" end

HighLine also has two definitions f get_character for Unix; you can copy one of these if you don want to require HighLine. The most reliable implementation is fairly complicated, and requires the termios gem. But if you need to require the termios gem, you might as well require the highline gem as well, and use HighLines implementation as is. So if you want to do single-character input on Unix without requiring any gems, youll need to rely on the Unix command stty:

def getch state = `stty -g` begin `stty raw -echo cbreak` $stdin.getc ensure `stty #{state}` end end while (c = getch) != ?e puts "You typed #{c.chr.inspect}" end

All of the HighLine code is in the main highline.rb file; search for "get_character".

See Also

Категории