Доступ к ключам с устройства ввода Linux
Что я пытаюсь сделать
Итак, я пытался получить доступ к вводу с клавиатуры в 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 будет ответом, но он, кажется, не поддерживает клавишу-модификатор 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
Мусор ASCII был отправлен на мой терминал при нажатии клавиши и отпускании событий, начиная с клавиши возврата (ввода), когда я начал вывод с помощью cat. Я также знаю, что это, кажется, работает хорошо с клавишами-модификаторами, такими как shift, control, function и даже с клавишей Apple на моем Macbook, работающем под управлением виртуальной машины Linux. Вывод появлялся при нажатии клавиши, быстро начинал появляться из последующих сигналов, посылаемых при удерживании клавиши, и выводил больше данных при отпускании клавиши.
Таким образом, хотя мой подход может быть неправильным (я готов услышать любую альтернативу), устройство, кажется, обеспечивает то, что мне нужно.
Кроме того, я знаю, что это устройство - просто ссылка, указывающая на /dev/input/event2 от запуска:
$ ls -l /dev/input/by-path/platform-i8042-serio-0-event-kbd
Я пробовал обе программы выше с /dev/input/event2 и не получил данных. Запуск cat в /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
тип)ev.type
:EV_KEY
ev.code
:KEY_*
ключевой идентификатор; см полный список в/usr/include/linux/input.h
ev.value
:0
если ключ отпущен,1
если нажать клавишу,2
если автоповторное нажатие
См. https://www.kernel.org/doc/Documentation/input/input.txt в исходных текстах ядра Linux для получения дополнительной информации.
Именованные константы в /usr/include/linux/input.h
довольно стабильны, потому что это интерфейс ядра-пользователя, и разработчики ядра очень стараются поддерживать совместимость. (То есть вы можете ожидать появления новых кодов время от времени, но существующие коды редко меняются.)