あらきけいすけのメモ帳

あらきけいすけの雑記帳2

Windows conio.h の kbhit(), getch() みたいな関数を Linux/Mac gcc で作る

授業のための覚書。コンソールでキーボード入力待ち状態を作り、入力が無い間は「idle process」を表示し、ASCII*1の印字可能文字(printable characters)のキーを押すと「[*]-key was hit」を表示し、ESCキーを押すと終了するイベント駆動型のC言語プログラム。
コード作成の目標はWindowsLinux/Macで同じ呼び出しで動作すること。
参考:linux C言語:ユーザーのライブラリを作る。makefile のテンプレート、getch(),kbhit()を例にとって。 #Linux - Qiita

#include <stdio.h>
#include <stdlib.h>
char keyData = '\0';

//----------------------------------------------
// #include <conio.h>
//----------------------------------------------
// mimic kbhit(), getch() in conio.h in Windows
#include <fcntl.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
char kbhit(void)
{
    struct termios old, new;
    tcgetattr(STDIN_FILENO, &old);
    new = old;
    new.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &new);
    int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    int byteswaiting = 0;
    ioctl(STDIN_FILENO, FIONREAD, &byteswaiting);
    keyData = '\0';
    if ( byteswaiting > 0 ) {
        keyData = getchar();
        fflush(stdout);
        printf("\b");
    }
      
    tcsetattr(STDIN_FILENO, TCSANOW, &old);
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    return keyData;
}
char getch (void)
{
    return keyData;
}
//----------------------------------------------
//#include <windows.h>
//----------------------------------------------
// mimic Sleep() in windows.h in Windows
void Sleep (int msec) {
    int usec = msec*1000;
    usleep(usec);
}
//----------------------------------------------

int execLoop = 1;

void keyEventProc(void)
{
    keyData = getch();
    if (keyData == 27) {
        printf("\rquit by [Esc]");
        fflush(stdout);
        execLoop = 0;
    } else if (32 <= keyData && keyData <= 126) {
        printf("\r[%c]-key was hit",keyData);
        fflush(stdout);
	Sleep(1000);
    }
}

void idleProc(void)
{
    printf("\ridle process   ");
    fflush(stdout);
}

int main(void)
{
    while (execLoop) {
        if (kbhit()) {
            keyEventProc();
        } else {
            idleProc();
        }
    }
    return 0;
}

kbhit()に関するChatGPT(3.5)の回答

質問

次のLinux上のC言語プログラムについて、必要なヘッダファイルと、各行の動作について教えてください

回答

このプログラムは、キーボード入力があるかどうかをチェックするための関数を実装しています。いくつかの関数や構造体が使用されていますので、必要なヘッダファイルと各行の動作を解説しますね。

必要なヘッダファイル:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>

それぞれの行の動作を解説します:

1. `struct termios old, new;`
- `struct termios`は、端末の属性を表す構造体です。`old`と`new`という名前の2つの変数を宣言しています。

2. `tcgetattr(STDIN_FILENO, &old);`
- 現在の端末属性を取得して、`old`構造体に格納しています。

3. `new = old;`
- 現在の端末属性をコピーして、`new`構造体にセットしています。

4. `new.c_lflag &= ~(ICANON | ECHO);`
- `new`構造体の `c_lflag` メンバーから `ICANON`(canonical モード)と `ECHO`(エコーバック)のビットをクリアしています。これにより、通常の入力モードやエコーバックが無効化されます。

5. `tcsetattr(STDIN_FILENO, TCSANOW, &new);`
- `tcsetattr()` 関数を使用して、変更した端末属性を適用します。

6. `int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);`
- ファイルディスクリプタ `STDIN_FILENO`(標準入力)のフラグを取得して、`oldf` 変数に保存します。

7. `fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);`
- `O_NONBLOCK` フラグをセットすることで、入力がなくてもプログラムがブロックされないようにします。

8. `int byteswaiting = 0;`
- 待機しているバイト数を格納する変数を宣言しています。

9. `ioctl(STDIN_FILENO, FIONREAD, &byteswaiting);`
- `FIONREAD` オプションを使用して、待機しているバイト数を取得します。

10. `keyData = '\0';`
- キーデータを格納する変数 `keyData` を初期化しています。

11. `if ( byteswaiting > 0 ) { ... }`
- 待機しているバイト数が0より大きい場合、キー入力があると判断しています。

12. `keyData = getchar();`
- `getchar()` を使用してキー入力を取得し、`keyData` に格納します。

13. `fflush(stdout);`
- 標準出力をフラッシュしています。

14. `printf("\b");`
- バックスペース文字を出力しています。この行は、キーボード入力を読み込んだ後、カーソルを1つ戻しています。

15. `tcsetattr(STDIN_FILENO, TCSANOW, &old);`
- プログラムの最後で、元の端末属性に戻しています。

16. `fcntl(STDIN_FILENO, F_SETFL, oldf);`
- 元のファイルフラグに戻します。

17. `return keyData;`
- キー入力データを返します。

この関数は、非同期でキーボード入力を監視し、キー入力がある場合はそのキーを返します。しかし、この関数は1回の呼び出しで1文字しか処理しません。