例えば簡単なCLIのゲームやpecoのようなキーボード入力が一回あるたびに何か動作させるものを作りたい場合は、OSの機能に働きかけ、端末の動作を変えることで実現できます。
Rubyでの場合を紹介しますが、
基本的にどのプログラミング言語でもこの端末の機能に触ることができれば実現できるはずです。
ルーク、io/consoleをつかえ
Rubyでは、標準添付ライブラリーio/console
で使えるようになるIO#rawを使用します。
主要なOSに対応しているのでwindowsでもlinuxでもmacで同じように使えるでしょう。
「ふつうにIO#getc
でいいじゃん」と思う方はirb
などでSTDIN.getc
を実行してみましょう。
$ irb
irb(main):001:0> STDIN.getc
asdf...
エンターキーを入力するまで文字の読み込みが始まらないことが分かります。
ではrequire 'io/console'
してからSTDIN.raw(&:getc)
を実行してみましょう。
$ irb
irb(main):001:0> require 'io/console'
=> true
irb(main):002:0> STDIN.raw(&:getc)
=> "a"
一文字入力した途端にirbに戻ってきます。
しくみ
端末には一般的にrawモードとcookedモードがあるようです。(他にもnoechoモードなどもあるが省略)
私たちが普段使っているようなものはcookedモードで、
- CTRL+Cなどのようにシグナルを送れる
- 一行分バッファリングするので途中でカーソル移動したり消したりできる
- 端末に入力した文字が表示される
という特徴があります。
逆にrawモードは
- シグナルを送れない
- バッファリングしない
- 端末に入力した文字を表示しない
という特徴があります。
Rubyでもこのモード切り替えが簡単に操作できるようになっていて、
-
IO#raw
: ブロックの間だけrawモードにする -
IO#cooked
: ブロックの間だけcookedモードにする
と使い分けることができます。
以降ずっとrawモードにするIO#raw!などもあるのですが、rawモードのままプログラムを終了すると、
コンソールの表示が変な感じになったりします。
ブロック付きで使うと、たとえエラーが起ころうとブロックを抜けるときに自動的に元の状態に戻してくれるのでおすすめです。
以下は入力したキーボードのバイト値を、
キーボードを打つ毎に端末の左上に表示するサンプルです。
#! /usr/bin/env ruby
require 'io/console'
$stdin.raw do |io|
while true
ch = io.readbyte
exit 0 if ch == 3 # CTRL+Cで終了
print "\e[H" # カーソルをコンソールの左上に移動する
print "\e[0K" # 前の文字が残らないようにする
print ch
end
end
このようにエスケープシーケンスなどと組み合わせれば、タイピングゲームやオレオレエディタなんかも作れて夢が広がります。
Cでの実装
基本的に#include <termios.h>
でつかえるAPIを利用します。
実践的な実装は https://github.com/ksss/mruby-io-console/blob/master/src/io-console.c あたりをご参考ください。
macでしか動作確認してない & win対応してないですが……。