web-dev-qa-db-ja.com

Linux入力デバイスからキーにアクセスする

私がやろうとしていること

だから、私はLinuxでキーボード入力にアクセスしようとしています。具体的には、修飾キーの押下にアクセスできる必要がありますwithout他のキーが押されています。さらに、私はこれを行うことができるようにしたいwithout Xシステムが実行されている。

要するに、私の要件は次のとおりです。

  • Linuxで動作します
  • X11は必要ありません
  • 修飾キーの押下を取得できますwithout他のキーが押されています
    • これには次のキーが含まれます。
      • シフト
      • コントロール
      • Alt
    • 必要なのは単純な0 = not pressed1 = currently pressedキーボードがチェックされているときにキーが押されているかどうかを知らせます

私のコンピューターのセットアップ

私の通常のLinuxマシンは、新しいアパートに向かうトラックに乗っています。ですから、今はMacbook Airしか使えません。したがって、私はこれをテストするためにVMでLinuxを実行しています。

VirtualBoxの仮想マシン

  • OS:Linux Mint 16
  • デスクトップ環境:XFCE

以下のすべてがこの環境で行われました。 Xを実行して、他のttyのいずれかで試しました。

私の考え

誰かが私を修正できる場合は、これを変更します。

高レベルのライブラリがこの種の機能を提供しないことを理解するために、私はかなりの読書をしました。修飾キーは、代替キーコードを提供するために他のキーとともに使用されます。 Linuxの高レベルライブラリを介して修飾キー自体にアクセスすることは、それほど簡単ではありません。または、むしろ、Linuxでこのための高レベルAPIが見つかりませんでした。

libtermkey が答えだと思いましたが、通常のキーストローク取得よりもShift修飾キーをサポートしているようには見えません。 Xがなくても動作するかどうかもわかりません。

Libtermkeyで作業している間(Shift-Returnのような場合にシフトが発生しないことに気付く前)、キーボードイベントを収集するために実行されるデーモンを作成することを計画していました。デーモンプログラムのコピーを実行すると、キーボードデータの要求がパイプ処理され、それに応じてキーボードデータが受信されます。特定の時間にキーコードのステータスを確認できない場合(発生時にキーコードを受信する必要がある場合)、このセットアップを使用して、常に何かをバックグラウンドで実行できます。

以下は、Linuxキーボードデバイスから読み取ることができるプログラムを作成しようとする2つの試みです。また、適切なデバイスが使用されていることを確認するための小さなチェックも含めました。

試み#1

キーボードデバイスに直接アクセスしようとしましたが、問題が発生しています。私は提案を試しました here それは別のStack Overflowスレッドにあります。セグメンテーションエラーが発生しました。そのため、fopenからopenに変更しました。

// ...

int fd;
fd = open("/dev/input/by-path/platform-i8042-serio-0-event-kbd", O_RDONLY);

char key_map[KEY_MAX/8 + 1];

memset(key_map, 0, sizeof(key_map));
ioctl(fd, EVIOCGKEY(sizeof key_map), key_map);

// ...

セグメンテーション違反はありませんでしたが、キーが押されたことを示すインジケーターはありませんでした(修飾キーだけでなく)。私はこれを次を使用してテストしました:

./foo && echo "TRUE" || echo "FALSE"

これを使用して、コマンドからの成功したリターンコードをかなり多くテストしました。だから、それでいいのはわかっている。また、チェックするキー(常に0)とマスク(0100)を出力しました。何も検出されないようです。

試み#2

ここから、少し違うアプローチを試してみようと思いました。私が間違っていたことを理解したかったのです。 this キーコードの出力を示すスニペットを提供するページに続いて、プログラムにバンドルしました。

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <linux/input.h>

int main(int argc, char** argv) {
    uint8_t keys[128];
    int fd;

    fd = open("/dev/input/by-path/platform-i8042-serio-event-kbd", O_RDONLY);
    for (;;) {
        memset(keys, 0, 128);
        ioctl (fd, EVIOCGKEY(sizeof keys), keys);

        int i, j;
        for (i = 0; i < sizeof keys; i++)
            for (j = 0; j < 8; j++)
                if (keys[i] & (1 << j))
                    printf ("key code %d\n", (i*8) + j);
    }

    return 0;
}

以前は、128バイトではなく16バイトのサイズでした。正直に言って、ioctlとEVIOCGKEYの理解にもう少し時間を費やすべきです。私はちょうどそれがおそらく特定のキーにビットをマップして押していること、またはそのようなことを示していることを知っています(間違っているなら私を修正してください!)。

また、最初はループがなかったため、さまざまなキーを押したままにして、キーコードが表示されるかどうかを確認しました。何も受け取りませんでした。そのため、何かを逃した場合にループを使用すると、チェックを簡単にテストできると考えました。

入力デバイスが正しいことをどのように知るか

入力デバイスでcatを実行してテストしました。具体的には:

$ Sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd

ガベージASCIIはキーを押して端末に送信され、catを使用して出力を開始したときにreturn(enter)キーで始まるイベントをリリースしました。これは修飾キーでも正常に動作するようです。 Linux VMを実行しているMacbookのシフト、コントロール、機能、さらにはAppleのコマンドキーなど。キーが押されると出力が表示され、キーを押し続けると送信される後続の信号から急速に表示され始め、キーがリリース。

そのため、私のアプローチは正しいアプローチではないかもしれませんが(any代替案を喜んで聞きます)、デバイスは必要なものを提供するようです。

さらに、このデバイスは、実行中の/ dev/input/event2を指す単なるリンクであることを知っています。

$ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd

上記の両方のプログラムを/ dev/input/event2で試してみましたが、データを受け取りませんでした。/dev/input/event2でcatを実行すると、リンクと同じ出力が提供されました。

24
Senkwich

入力デバイスを開き、

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

static const char *const evval[3] = {
    "RELEASED",
    "PRESSED ",
    "REPEATED"
};

int main(void)
{
    const char *dev = "/dev/input/by-path/platform-i8042-serio-0-event-kbd";
    struct input_event ev;
    ssize_t n;
    int fd;

    fd = open(dev, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Cannot open %s: %s.\n", dev, strerror(errno));
        return EXIT_FAILURE;
    }

次に、デバイスからreadキーボードイベント:

    while (1) {
        n = read(fd, &ev, sizeof ev);
        if (n == (ssize_t)-1) {
            if (errno == EINTR)
                continue;
            else
                break;
        } else
        if (n != sizeof ev) {
            errno = EIO;
            break;
        }

上記のスニペットは、エラーが発生した場合、またはユーザー空間が部分的なイベント構造のみを受信した場合(発生しないはずですが、将来の/バグのあるカーネルで発生する可能性があります)、ループから抜け出します。より堅牢な読み取りループを使用することもできます。個人的には、最後のbreakcontinueに置き換えることで満足するので、部分的なイベント構造は無視されます。

その後、evイベント構造を調べて、何が発生したかを確認し、プログラムを終了できます。

        if (ev.type == EV_KEY && ev.value >= 0 && ev.value <= 2)
            printf("%s 0x%04x (%d)\n", evval[ev.value], (int)ev.code, (int)ev.code);

    }
    fflush(stdout);
    fprintf(stderr, "%s.\n", strerror(errno));
    return EXIT_FAILURE;
}

キーを押すと、

  • ev.time:イベントの時間(struct timevalタイプ)

  • ev.typeEV_KEY

  • ev.codeKEY_*、キー識別子; /usr/include/linux/input.hの完全なリストを参照してください

  • ev.value0キーが離された場合、1キーが押された場合、2キーが自動押された場合

詳細については、Linuxカーネルソースの Documentation/input/input.txt を参照してください。

/usr/include/linux/input.hの名前付き定数は非常に安定しています。これはカーネルとユーザー空間のインターフェースであり、カーネル開発者は互換性を維持するために非常に懸命に努力しています。 (つまり、時々新しいコードが存在することを期待できますが、既存のコードはめったに変更されません。)

37
Nominal Animal