やりたいこと
- WindowsのC言語では kbhit() と getch() を使ってキーボード入力を即時に取得できる。
- LinuxのC言語ではこれらの関数は用意されておらず、キーボード入力は1行ごとにバッファされ、Enterキーが入力されるまで取得できない。
- Linuxでも kbhit() と getch() 相当の機能を実現したい。
ちなみに、Windowsでも現在では kbhit() と getch() は非推奨の関数名で、代わって _kbhit() と _getch() を使う。
#include <stdio.h> #include <conio.h> int main() { while (1) { while (!_kbhit()) {;} int ch = _getch(); printf("kbhit: %c\n", ch); if (ch == 'q') break; } printf("\nQuit\n"); return 0; }
方法
- 方法はいろいろあるが、基本的には tcgetattr() / tcsetattr() でターミナルの設定を変更する。
- 後述の参考サイトのソースを参考に実装した。
- 処理の無駄を省いたかわり、最初に kb_begin()、最後に kb_end() を実行する。
- 無駄を省いた作りにしたので使い方を間違えないように注意。
- スレッドセーフでない作りだが、そもそもキーボード入力を複数のスレッドから受けることはないはず。
ソース
kbhit.h
#pragma once void kb_begin(void); void kb_end(void); int kbhit(void); char getch(void);
kbhit.c
#include <stdio.h> #include <termios.h> #include <unistd.h> #include "kbhit.h" static struct termios g_old_set; // ターミナルの元の設定 static int g_read_char = -1; // 読みだした文字 (-1のとき無効) // 開始する // (標準入力を、即時、エコーバックなし、ノンブロッキングに設定) void kb_begin(void) { // 現在の標準入力の設定を取得 tcgetattr(STDIN_FILENO, &g_old_set); // 標準入力の設定を変更 struct termios new_set = g_old_set; new_set.c_lflag &= ~ICANON; // 非カノニカル(改行を待たず即時入力) new_set.c_lflag &= ~ECHO; // エコーバックなし // new_set.c_lflag &= ~ISIG; // シグナル発生なし (Ctrl-Cなどの) new_set.c_cc[VMIN] = 0; // 待ち受けしない (待ち受け文字数ゼロ) new_set.c_cc[VTIME] = 0; // 待ち受けしない (待ち受け時間ゼロ) tcsetattr(STDIN_FILENO, TCSANOW, &new_set); } // 終了する // (キーボード入力の設定を元に戻す) void kb_end(void) { // ターミナルの設定を戻す tcsetattr(STDIN_FILENO, TCSANOW, &g_old_set); } // キーボード入力があったかチェック // return: 0:なし / 1:あり int kbhit(void) { if(g_read_char != -1) return 1; // すでに入力があった // 標準入力から1文字読み出し char ch; int num = read(STDIN_FILENO, &ch, 1); // 読み出せたか? if(num == 1) { g_read_char = ch; return 1; }else{ return 0; } } // キーボード入力を1文字取得 char getch(void) { // すでに読みだした文字があればそれを返す if(g_read_char != -1) { char ch = g_read_char; g_read_char = -1; return (ch); } // なければ1文字読み出す (読み出せなければゼロを返す) else{ char ch = 0; read(STDIN_FILENO, &ch, 1); return(ch); } }
使い方
main.c
#include <stdio.h> #include "kbhit.h" int main() { kb_begin(); while (1) { while (!kbhit()) {;} char ch = getch(); printf("kbhit: %c\n", ch); if (ch == 'q') break; } kb_end(); printf("\nQuit\n"); return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.13) project(kbhit_test C) add_executable(kbhit_test main.c kbhit.c)
参考