Доступ к ключам с устройства ввода Linux
Что я пытаюсь сделать
Итак, я пытался получить доступ к клавиатуре в Linux. В частности, мне нужно иметь доступ к нажатию клавиш модификатора без других нажатых клавиш. Кроме того, я хочу иметь возможность без работать с системой X.
Итак, короче говоря, мои требования таковы:
- Работает в Linux
- Не нужен X11
- Можно извлечь ключ-модификатор нажатием без любых других нажатых клавиш
- Это включает в себя следующие ключи:
- Все, что мне нужно, это просто
0 = not pressed
, 1 = currently pressed
, чтобы сообщить мне, если
клавиша удерживается, когда клавиатура проверена.
Настройка моего компьютера
Моя нормальная машина Linux находится на грузовике к моей новой квартире; поэтому у меня есть только Macbook Air для работы прямо сейчас. Поэтому я запускаю Linux в виртуальной машине, чтобы проверить это.
Виртуальная машина в VirtualBox
- ОС: Linux Mint 16
- Окружающая среда для рабочего стола: XFCE
Все, что было сделано в этой среде. Я пробовал работать с X и одним из других ttys.
Мои мысли
Я изменю это, если кто-то может исправить меня.
Я сделал честное чтение, чтобы понять, что библиотеки более высокого уровня не обеспечивают такую функциональность. Клавиши-модификаторы используются с другими клавишами для обеспечения альтернативного key code. Доступ к самим ключам-модификаторам через библиотеку высокого уровня в Linux не так просто. Или, скорее, я не нашел API высокого уровня для этого в Linux.
Я думал, что libtermkey будет ответом, но, похоже, он не поддерживает Shift, лучше, чем обычный поиск нажатий клавиш. Я также не уверен, что он работает без X.
Во время работы с libtermkey (до того, как я понял, что он не изменился в таких случаях, как Shift-Return), я планировал написать демон, который будет запускаться для сбора событий клавиатуры. Запуск копий программы-демона просто направляет запросы на ввод данных с клавиатуры и получает данные клавиатуры. Я мог бы использовать эту настройку для того, чтобы что-то всегда работало в фоновом режиме, если я не могу проверить статусы key code в определенное время (должны быть получены коды клавиш, как они бывают).
Ниже приведены две мои попытки написать программу, которая может считываться с устройства на клавиатуре 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. Я просто знаю, что он предположительно отображает бит на определенные клавиши, чтобы указать прессу, или что-то в этом роде (исправьте меня, если я ошибаюсь, пожалуйста!).
У меня также не было петли изначально и просто удерживал различные клавиши, чтобы увидеть, появился ли key code. Я ничего не получил; поэтому, я думал, что цикл может сделать проверку более легкой для тестирования в случае пропущенного.
Как я знаю, что устройство ввода является правильным.
Я протестировал его, запустив cat
на устройстве ввода. В частности:
$ sudo cat /dev/input/by-path/platform-i8042-serio-0-event-kbd
Garbage ASCII был отправлен на мой терминал при нажатии клавиши и отпускании, начиная с клавиши возврата (ввода), когда я начал вывод с помощью cat. Я также знаю, что это отлично работает с клавишами-модификаторами, такими как shift, control, function и даже клавиша Apple key на моем Macbook с виртуальной машиной Linux. Выход появился при нажатии клавиши, стал быстро появляться от последующих сигналов, посланных нажатием клавиши вниз, и выдавал больше данных, когда был выпущен ключ.
Итак, хотя мой подход может быть не правильным (я готов выслушать любую альтернативу), устройство, похоже, предоставляет мне то, что мне нужно.
Кроме того, я знаю, что это устройство - это просто ссылка, указывающая на запуск /dev/input/event 2:
$ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd
Я пробовал обе программы выше с /dev/input/event 2 и не получал никаких данных. Запуск cat on/dev/input/event2 предоставил тот же результат, что и для ссылки.
Ответы
Ответ 1
Откройте устройство ввода,
#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
, если autorepeat keypress
Подробнее см. Documentation/input/input.txt в источниках ядра Linux.
Именованные константы в /usr/include/linux/input.h
довольно стабильны, потому что это интерфейс для ядра и пользовательского пространства, и разработчики ядра очень стараются поддерживать совместимость. (То есть вы можете ожидать, что там будут новые коды время от времени, но существующие коды редко меняются.)