自作エディターを作る! キー入力監視スレッドの改善(unixでのkbhit()の実装)

前回の記事では、getchar()タイムアウトありで呼ぶようにして、1byteずつ取得した文字をKEY構造体に突っ込んでいたんですが、そちらを辞めにしました。 kbhit()関数を新たに作成し、入力がある場合はread()でバッファをまるごと取得し、ない場合はしばらくスリープして次のスキャンで取得する方針としました。

r-mutax.hateblo.jp

こんかいやったこと

スレッド停止ができない!

ことの発端はこのツイートです。

std::future.wait_for()を使ってgetchar()のラッパー関数をタイム・アウトさせることで、キー入力処理のタイムアウトを監視し、タイムアウトした=キー入力の切れ目を認識することで、マルチバイト文字の切れ目を認識できるようにしていました。

ところが、このwait_for()ですが、いくら呼び出し元スレッドでタイムアウトを検知できたからと行ってスレッドが終了するわけでもなく、getchar()でずっとキー入力を待ち続けているみたいでした。おそらくstdinのバッファになにか突っ込めばとりあえずごまかしで終了させられるとは思うのですが…。

それは作り的に気持ちが悪いので、kbhit()関数を使って単純にループを回すことにしました。

仮想キーコードの導入

キー入力イベントを作って情報を渡すときに、KEY構造体を渡すのが面倒でした。 イベント創出側でmallocしてからイベント処理側でいちいちfreeしないといけないし…。

そこでキーイベント監視スレッドで仮想キーへ変換し、イベントメッセージには仮想キーを送ることにしました。これで余計なmalloc/freeもしなくていいですし、今後Windows側で今回のソースを使うときにも、移植がしやすくなるのでは?と思います。

実装

kbhit()

こちらの記事を参考にしました。

ioctl()関数は端末制御用関数です。ここではFIONREADを第2引数に指定して、byteswaitingstdinのバッファで入力待ちをしている文字数を取得するようにしています。 もし入力待ちの文字がある場合は、trueを返します。 なお参考リンクではioctl()を使う前に端末をcookedモードに切り替えていますが、今回作成しているエディタでは初期化時にcookedモードに切り替えて終了時に戻しているので、あえてkbhit()では変えていません。

#include <sys/ioctl.h>

bool kbhit(){
    int byteswaiting;
    ioctl(0, FIONREAD, &byteswaiting);
    return byteswaiting > 0;
}

get_vkcode()

仮想キーの取得関数です。 流れとしては、kbhit()でキーの入力ありか確かめ、ある場合はread()でバッファを全部読み、ない場合は30msecスリープします。

入力を読み取れた場合は、vkcode_conv()で仮想キーへ変換し、コール元へ返します。

char get_vkcode(){

    std::vector<char>   keybuf;
    bool inputend = false;

    while(1){
        if(kbhit()){
            char key[10] = {};
            read(0, key, 10);
            
            for(int i = 0; i < 10; i++){
                if(key[i] == 0) break;
                keybuf.emplace_back(key[i]);
            }
            break;
        }
        
        usleep(30000);
    }

    return vkcode_conv(keybuf);
}

次回予告

次回ですが、仮想キーの実装もできたので、仮想キーコードでカーソルを動かすところまでやっていこうと思います。 というかもうできているので、整理してpushして記事にします。 ちなみにですが、カーソルがなかなか表示できなくて困ってました。

参考文献

c++ — Linuxでのkbhit()およびgetch()の使用

Man page of IOCTL

Man page of READ