2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Linux環境のPythonでmsvcrt的なkbhitやgetchをつくりた~い! ~挫折編~

Posted at

はじめに

こんにちは、麻菜結と申します。最近はPythonで外部モジュールに頼らず色々実装してみる縛りプレイのようなものをしているのですが、環境依存の少ないクロスプラットフォームなコードの中でキーボードイベントの取得をする必要が出てきたのでそれに挑戦した記録です。

どんなものをつくるのか

先ほどmsvcrtkbhitgetchと言いましたが、何を作るのか具体的に示したいと思います。関数名は同じにしたいのであえてラップしたような関数として記述します。

kbhitの方はそのまんまですね。

kbhit的なやつ.py
# 呼び出すとキーボードが入力されているかを返す関数。
import msvcrt
def keyboardHit():
    return msvcrt.kbhit()

getch的なやつの方は、入力されていたらそのキーを、そうでは無かったら-1を返して欲しい関数です。

getch的なやつ.py
import msvcrt
def getKeyboardCharactor():
    if keyboardHit():
        return int.from_bytes(msvcrt.getch(), byteorder="big")
    return -1

環境

$ python --version
Python 3.7.3
$ cat /proc/version
Linux version 5.4.151-16906-g86cbb761e8c4 (chrome-bot@chromeos-ci-legacy-us-central1-b-x32-130-ws8g) (Chromium OS 13.0_pre428724_p20210813-r7 clang version 13.0.0 (/var/tmp/portage/sys-devel/llvm-13.0_pre428724_p20210813-r7/work/llvm-13.0_pre428724_p20210813/clang 9968896cd62a62b11ac61085534dd598c4bd3c60)) #1 SMP PREEMPT Sun Nov 7 19:34:19 PST 2021

お気づきでしょうか。実行環境はchromeOSのcrostini、つまりコンテナ。なのでLinux特有のデバイスファイルを見れば生の情報があるんだからそれ読めば良いじゃんが使えません。

方策

じゃあどうするんだよと言う話なのですが、Pythonのtermiosttyselectとを駆使してなんとかします。と言っても実はこのstackoverflowのコードの改変なのですが。
Non-blocking console input?
簡単に流れを説明すると、

  1. 通常の標準入力の状態を保持する
  2. CBREAKモードという入力バッファを用いない(≒エンターキーを押す必要がない)モードへ変更する。
  3. selectというIO処理の完了を待機する関数を用いて標準入力が読み込み可能か検証する。
  4. 3の処理を待って1文字でも書き込まれたら読み出す。
  5. 終了時には1で保持した通常の標準入力の状態をもとにCBREAモードKから抜け出す。

という感じですね。英語が読めないのでコードを何度も咀嚼した結果多分こうなんだろうなというものですが。

コード

それでは方策をもとに記述したコードをお見せします。

nonblock_test.py
import sys
import select
import tty
import termios

# 3. selectは難しいから公式のリファレンスマニュアルみて
def keyboardHit():
    return select.select([sys.stdin],[],[],0)==([sys.stdin],[],[])
# 4. -1を出力するためにkeyboardHit関数は結局必要
def getKeyboardCharctor():
    return ord(sys.stdin.read(1)) if keyboardHit() else -1

# 1. 通常状態保持
old_fn = sys.stdin.fileno()
old_tt = termios.tcgetattr(old_fn)
# 2. CBREAKモードへ変更
tty.setcbreak(old_fn)
try:
    print(getKeyboardCharctor())                              # これはちゃんと-1が出力されるかのテスト
    while True:
        if keyboardHit():
            v = getKeyboardCharctor()
            print(v)
            if(v == 27):                                      # エスケープキーで終了
                break
finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tt)   # 5.通常状態へ回復

動かしてみるとそれっぽく動くと思いますが、この動作にはCBREAKモードへの変更とそれからの回復がキモで、プログラムの始まりと終わりに125の工程が欠かせません。つまり、kbhitgetchみたいなモジュールインポートしてサクッと使うみたいなことが出来ず、標準入力の状態を変更するのでプログラム内だと多分inputとかが上手く動きません(エコーバックがなかったりする)。

おわりに

今回は結局作りたいものは作れなかったよという記事ですが、これはこれで使いようがありそうではあります。日本語で書かれたノンブロッキング入力の情報があまりみあたらなかったような気がするので、まあそういう意味でも意義があったら嬉しいですね。「縛りプレイなんかせずPygame使えよ」とか言わない
ここまでお読みくださってありがとうございました。何かお役に立ったなら幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?