Доступ к ключам с устройства ввода Linux

17

Что я пытаюсь сделать

Итак, я пытался получить доступ к клавиатуре в Linux. В частности, мне нужно иметь доступ к нажатию клавиш модификатора без других нажатых клавиш. Кроме того, я хочу иметь возможность без работать с системой X.

Итак, короче говоря, мои требования таковы:

  • Работает в Linux
  • Не нужен X11
  • Можно извлечь ключ-модификатор нажатием без любых других нажатых клавиш
    • Это включает в себя следующие ключи:
      • Сдвиг
      • Управление
      • Alt
    • Все, что мне нужно, это просто 0 = not pressed , 1 = currently pressed , чтобы сообщить мне, если клавиша удерживается, когда клавиатура проверена.

Настройка моего компьютера

Моя нормальная машина Linux находится на грузовике к моей новой квартире; поэтому у меня есть только Macbook Air для работы прямо сейчас. Поэтому я запускаю Linux в виртуальной машине, чтобы проверить это.

Виртуальная машина в VirtualBox

  • ОС: Linux Mint 16
  • Окружающая среда для рабочего стола: XFCE

Все, что было сделано в этой среде. Я пробовал работать с X и одним из других ttys.

Мои мысли

Я изменю это, если кто-то может исправить меня.

Я сделал честное чтение, чтобы понять, что библиотеки более высокого уровня не обеспечивают такую ​​функциональность. Клавиши-модификаторы используются с другими ключами для предоставления альтернативного кода ключа. Доступ к самим ключам-модификаторам через библиотеку высокого уровня в Linux не так просто. Или, скорее, я не нашел API высокого уровня для этого в Linux.

Я думал, что libtermkey будет ответом, но он не " t, похоже, поддерживает ключ модификатора Shift лучше, чем обычное извлечение клавиш. Я также не уверен, что он работает без X.

Во время работы с libtermkey (до того, как я понял, что он не изменился в таких случаях, как Shift-Return), я планировал написать демон, который будет запускаться для сбора событий клавиатуры. Запуск копий программы-демона просто направляет запросы на ввод данных с клавиатуры и получает данные клавиатуры. Я мог бы использовать эту настройку для того, чтобы что-то всегда работало в фоновом режиме, если я не могу проверять состояния кода ключа в определенное время (нужно получать коды клавиш, как они есть).

Ниже приведены две мои попытки написать программу, которая может считываться с устройства на клавиатуре Linux. Я также включил мою небольшую проверку, чтобы убедиться, что у меня есть подходящее устройство.

Попытка # 1

Я попытался получить доступ к клавиатурному устройству напрямую, но столкнулся с проблемами. Я пробовал предложение здесь , которое находится в другом потоке переполнения стека. Это дало мне ошибку сегментации; поэтому я изменил его из fopen, чтобы открыть:

// ...

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

Отсюда я подумал, что попробую несколько иной подход. Я хотел выяснить, что я делаю неправильно. Следуя этой странице, предоставляющей фрагмент, демонстрирующий распечатку кодов клавиш, я связал это в программу:

#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;
}

Раньше у меня был размер до 16 байтов вместо 128 байтов. Я должен честно потратить немного больше времени на понимание ioctl и EVIOCGKEY. Я просто знаю, что он предположительно отображает бит на определенные клавиши, чтобы указать прессу, или что-то в этом роде ( исправьте меня, если я ошибаюсь, пожалуйста! ).

У меня также не было петли изначально и просто удерживал различные ключи, чтобы увидеть, появился ли код клавиши. Я ничего не получил; поэтому, я думал, что цикл может сделать проверку более легкой для тестирования в случае пропущенного.

Как я знаю, что устройство ввода является правильным.

Я протестировал его, выполнив cat на устройстве ввода. В частности:

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

Garbage ASCII был отправлен на мой терминал при нажатии клавиши и отпускании, начиная с клавиши возврата (ввода), когда я начал вывод с помощью cat. Я также знаю, что это, похоже, отлично работает с клавишами-модификаторами, такими как shift, control, function и даже командный ключ Apple на моем Macbook с Linux VM. Выход появился при нажатии клавиши, стал быстро появляться от последующих сигналов, посланных нажатием клавиши вниз, и выдавал больше данных, когда был выпущен ключ.

Итак, хотя мой подход может быть не правильным (я готов услышать любую альтернативу ), устройство, похоже, обеспечивает то, что мне нужно.

Кроме того, я знаю, что это устройство - это просто ссылка, указывающая на запуск / dev / input / event2:

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

Я пробовал обе программы выше с / dev / input / event2 и не получал никаких данных. Запуск cat on / dev / input / event2 предоставил тот же результат, что и для ссылки.

    
задан Senkwich 06.01.2014 в 06:07
источник
  • Быстрый отзыв (у меня нет реального ответа): Вы можете распечатать код состояния предыдущей команды в bash с помощью echo $ ?. Возможно сохранение некоторых типов. :) –  luser droog 06.01.2014 в 06:46
  • Не знал этого. Благодаря! –  Senkwich 06.01.2014 в 07:38

1 ответ

33

Откройте устройство ввода,

#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;
    }

, а затем прочитать события клавиатуры с устройства:

    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;
        }

Вышеприведенный фрагмент выходит из цикла, если возникает какая-либо ошибка, или если пользовательское пространство получает только частичную структуру событий (что не должно происходить, но может быть в некоторых будущих / багги-ядрах). Возможно, вы захотите использовать более надежный цикл чтения; Я лично был бы доволен заменой последнего break continue , так что частичные структуры событий игнорируются.

Затем вы можете просмотреть структуру событий 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 type)

  • ev.type : EV_KEY

  • ev.code : KEY_* , идентификатор ключа; см. полный список в /usr/include/linux/input.h

  • ev.value : 0 , если ключ освобожден, 1 , если нажать клавишу, 2 , если автозапуск нажат

Подробнее см. Documentation / input / input.txt в источниках ядра Linux.

Именованные константы в /usr/include/linux/input.h довольно стабильны, поскольку это интерфейс ядра и пользовательского пространства, а разработчики ядра очень стараются поддерживать совместимость. (То есть вы можете ожидать, что там будут новые коды время от времени, но существующие коды редко меняются.)

    
ответ дан Nominal Animal 06.01.2014 в 09:48
  • Отлично! Я смотрел на документацию, которую вы упомянули, но не желал углубляться дальше, если бы искал не то место. Отличные отрывки и объяснения! Тестирование кода, который вы мне дали, работает так, как мне это нужно! –  Senkwich 06.01.2014 в 21:31
  • В моей системе я должен быть суперпользователем, чтобы получить доступ к клавиатурному устройству. Есть ли другой путь? Также моя консоль эхо выводит на экран. Какой способ рекомендуется избегать эха? –  muman 21.08.2015 в 03:13
  • @muman: Ну, вы можете добавить правило udev, чтобы изменить право собственности и режим устройства. Вы определенно НЕ хотите, чтобы устройства ввода могли быть прочитаны каждым, потому что это сделало бы подслушивание очень легким. Обычно процесс обработки ввода / демон запускается с привилегиями суперпользователя; с непривилегированными вспомогательными утилитами для связи с ним. Другими словами, базовый дизайн Unix. –  Nominal Animal 21.08.2015 в 19:20
  • @muman: вы можете захватить (ioctl (fd, EVIOCGRAB, 1)) устройство ввода событий, чтобы потреблять нажатия клавиш (хватайте их, а не просто наблюдайте за ними). См. Мой пример здесь, особенно функцию barcode_open (). Вы можете повторно вставлять любые нажатия клавиш с помощью устройства uinput (но для получения очевидных соображений безопасности тоже нужны привилегии). –  Nominal Animal 21.08.2015 в 19:21