web-dev-qa-db-ja.com

Cノンブロッキングキーボード入力

ユーザーがキーを押すまでループするプログラムをC(Linux)で記述しようとしていますが、各ループを続行するためにキーを押す必要はありません。

これを行う簡単な方法はありますか?おそらくselect()でできると思いますが、それは大変な作業のようです。

または、キャッチする方法はありますか ctrl-c ノンブロッキングioの代わりにプログラムが閉じる前にクリーンアップを実行するキープレス?

75
Zxaos

すでに述べたように、sigactionを使用してctrl-cをトラップするか、selectを使用して標準入力をトラップできます。

ただし、後者の方法では、行単位モードではなく文字単位モードになるようにTTYを設定する必要があることに注意してください。後者がデフォルトです-テキスト行を入力した場合、実行キーを押すまで実行中のプログラムの標準入力に送信されません。

tcsetattr()関数を使用してICANONモードをオフにし、おそらくECHOも無効にする必要があります。メモリから、プログラムの終了時に端末をICANONモードに戻す必要もあります!

完全を期すために、Unix TTYをセットアップし、DOS <conio.h>関数kbhit()およびgetch()をエミュレートするコードをいくつか紹介します(nb:エラーチェックなし!)。 :

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
        /* do some work */
    }
    (void)getch(); /* consume the character */
}
60
Alnitak

UNIXシステムでは、sigaction呼び出しを使用して、Control + Cキーシーケンスを表すSIGINTシグナルのシグナルハンドラを登録できます。シグナルハンドラーは、ループでチェックされるフラグを設定して、適切にブレークすることができます。

11
Mehrdad Afshari

おそらくkbhit();が必要です

_//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}
_

これはすべての環境で機能するとは限りません。ポータブルな方法は、監視スレッドを作成し、getch();にフラグを設定することです

6
Jon Clegg

Cursesライブラリーはこの目的に使用できます。もちろん、select()とシグナルハンドラーもある程度使用できます。

3
PolyThinker

ノンブロッキングキーボード入力を取得する別の方法は、デバイスファイルを開いて読み取ることです!

/ dev/input/event *の1つを探しているデバイスファイルを知っている必要があります。 cat/proc/bus/input/devicesを実行して、必要なデバイスを見つけることができます。

このコードは私のために機能します(管理者として実行)。

  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <linux/input.h>

  int main(int argc, char** argv)
  {
      int fd, bytes;
      struct input_event data;

      const char *pDevice = "/dev/input/event2";

      // Open Keyboard
      fd = open(pDevice, O_RDONLY | O_NONBLOCK);
      if(fd == -1)
      {
          printf("ERROR Opening %s\n", pDevice);
          return -1;
      }

      while(1)
      {
          // Read Keyboard Data
          bytes = read(fd, &data, sizeof(data));
          if(bytes > 0)
          {
              printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
          }
          else
          {
              // Nothing read
              sleep(1);
          }
      }

      return 0;
   }
3
JustinB

Control-Cをキャッチするだけで満足している場合は、これで完了です。本当に非ブロッキングI/Oが必要で、cursesライブラリが必要ない場合は、ロック、ストック、およびバレルを AT&T sfio library に移動することもできます。 C stdioでパターン化された素晴らしいライブラリですが、より柔軟で、スレッドセーフで、パフォーマンスが向上しています。 (sfioは安全で高速なI/Oの略です。)

2
Norman Ramsey

これを行うための移植可能な方法はありませんが、select()は良い方法かもしれません。 http://c-faq.com/osdep/readavail.html をご覧ください。

1
Nate879

これを行うための関数を次に示します。 POSIXシステムに付属のtermios.hが必要です。

#include <termios.h>
void stdin_set(int cmd)
{
    struct termios t;
    tcgetattr(1,&t);
    switch (cmd) {
    case 1:
            t.c_lflag &= ~ICANON;
            break;
    default:
            t.c_lflag |= ICANON;
            break;
    }
    tcsetattr(1,0,&t);
}

これを分解すると、tcgetattrは現在の端末情報を取得し、tに保存します。 cmdが1の場合、tのローカル入力フラグは非ブロッキング入力に設定されます。それ以外の場合はリセットされます。次に、tcsetattrは標準入力をtに変更します。

プログラムの最後で標準入力をリセットしないと、シェルで問題が発生します。

0
112