С 2016 года я неспешно интересовался вопросом, как подключить к беспилотному unix-серверу клавиатурку так, чтобы с нее шли управляющие команды в нужное приложение. И вот после недавнего обсуждения я наконец состряпал нужное и поставил на боевое дежурство - исправно работает уже несколько недель. Огромное спасибо за помощь Byte и Dmitry Shmidt! Теперь поделюсь итоговыми результатами, может, кому пригодится.
Софтинка запускается как крохотный демон, почти не занимающий оперативной памяти, который потом (при нажатии кнопки, что ожидается редко) вызывает скрипт-обработчик и передает ему, что было нажато, а тот уж пуст разбирается, как поступить. Поэтому в качестве первого аргумента софтинке передается Vendor/Product нужного USB-устройства через двоеточие (например "1C4F:0002), либо путь до устройства в системе (вида "/dev/input ..." - я не тестировал, но и это должно работать тоже). В качестве второго аргумента в кавычках - передам команду, которая будет выполнена в системе, конструкция %c в ней будет заменена на введенный символ. Например: "echo \"%c\" >> /tmp/111.txt" или "notify-send -t 300 \"KeyCode: %c\"" или так:
sudo /home/lleo/daemons/kyeboardnoid 1C4F:0002 "/home/lleo/do_commands.php \"SCANCODE=%c\""
При отваливании из USB устройства и появлении его заново - корректно обрабатывает эти неполадки, терпеливо ждёт, поглядывая. Полный код софтинки:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <dirent.h>
#include <linux/input.h>
#define VENDORID 0x1c4f
#define PRODUCTID 0x0002
int16_t fd = -1;
char path[300];
int init_keyboard(unsigned int VENDOR,unsigned int PRODUCT) {
fd=-1;
int count=0;
struct dirent **files=NULL;
struct input_id id;
count = scandir("/dev/input",&files,0,0)-1;
while(count>=0) {
if (fd==-1 && strncmp(files[count]->d_name,"event", 5)==0) {
sprintf(path,"/dev/input/%s",files[count]->d_name);
fd = open(path,O_RDONLY);
if(fd>=0) {
if(ioctl(fd,EVIOCGID,(void *)&id)<0 ) perror("ioctl EVIOCGID");
else {
if(id.vendor==VENDOR && id.product==PRODUCT && id.bustype==BUS_USB) { printf("Device found at %s ",path); }
else {
close(fd);
fd = -1;
}
}
} else {
fprintf(stderr,"Error opening %s:\t",path); perror("");
}
}
free(files[count--]);
}
free(files);
int grab=1;
if(fd>=0) ioctl(fd,EVIOCGRAB,&grab);
else {
fprintf(stderr,"Device not found or access denied. ");
return EXIT_FAILURE;
}
return 0;
}
char remap_codes(int scancode){
char r=' ';
switch(scancode) {
// NumLock = off + N // NumLock = on
case 0x45: r=0; break;
// 000 -> 0 0 0 // 000 -> --- NO ---
case 0x52: r='0'; break; case 0x6E: r='0'; break;
case 0x4f: r='1'; break; case 0x6B: r='1'; break;
case 0x50: r='2'; break; case 0x6C: r='2'; break;
case 0x51: r='3'; break; case 0x6D: r='3'; break;
case 0x4b: r='4'; break; case 0x69: r='4'; break;
case 0x4c: r='5'; break; //--- NO ---
case 0x4d: r='6'; break; case 0x6A: r='6'; break;
case 0x47: r='7'; break; case 0x66: r='7'; break;
case 0x48: r='8'; break; case 0x67: r='8'; break;
case 0x49: r='9'; break; case 0x68: r='9'; break;
case 0x53: r='.'; break; case 0x6F: r='.'; break;
case 0x60: r='E'; break; // Enter
case 0x62: r='C'; break; // /
case 0x37: r='Z'; break; // *
case 0x4a: r='M'; break; // -
case 0x4e: r='P'; break; // +
case 0x0E: r='B'; break; // Backspace
default:
printf("Scancode:0x%X - [%c]\n",scancode,(unsigned char)scancode);
r=(unsigned char)scancode;
}
return r;
}
char read_keyboard() {
struct input_event ev;
ssize_t n;
while(true) {
n = read(fd, &ev, sizeof (struct input_event));
if(n == (ssize_t)-1) {
if (errno == EINTR) continue;
else break;
} else if(n != sizeof ev) {
errno = EIO;
break;
}
if( ev.type==EV_KEY && ev.value==EV_KEY ) return remap_codes(ev.code);
}
return '!';
}
int connect_keyboard(int argc,char* arg) {
char name[256] = "Unknown";
if(argc>1) {
// пробуем отсканировать VEND:PROD
unsigned int VEND=VENDORID,PROD=PRODUCTID;
if(2==sscanf(arg,"%x:%x",&VEND,&PROD)) {
printf("\nConnecting to device %04x:%04x... ",VEND,PROD);
if(init_keyboard(VEND,PROD)!=0) { printf("FAILED\n"); return(1); }
printf("OK\n");
} else {
// пробуем найти path
snprintf(path,200,"%s",arg);
printf("\nConnecting to device %s ... ",path);
if((fd = open(path,O_RDONLY)) == -1) { printf("FAILED\n"); return(1); }
printf("OK\n");
}
} else { // откроем дефолтный девайс
printf("\nConnecting to default device %04x:%04x... ",VENDORID,PRODUCTID);
if(init_keyboard(VENDORID,PRODUCTID)!=0) { printf("FAILED\n"); return(1); }
printf("OK\n");
}
ioctl(fd, EVIOCGNAME (sizeof (name)), name);
fprintf(stderr, "Reading from: %s (%s)\n", path, name);
return 0;
}
int main(int argc, char* argv[]) {
printf("Runnung... (Example: \"sudo ./keyboardnoid 1C4F:0002 \"echo \\\"%%c\\\" >> /tmp/1 11.txt\")");
char r=0;
char action[300]="notify-send -t 300 \"KeyCode: %c\"";
if(argc>2) snprintf(action,1024,"%s",argv[2]); // скопировать строку акции
// коннектимся
while(1) { if(0 == connect_keyboard(argc,argv[1])) break; sleep(2); } // 1 сек
while(true) {
if(!(r=read_keyboard())) continue;
// printf("Scancode:0x%X - [%c]\n",r,(unsigned char)r);
if(r=='!') { // обрыв связи
sleep(1);
close(fd); printf("port_disconnected\n");
snprintf(path,100,action,'?'); system(path); // подать сигнал
while(true) { if(0 == connect_keyboard(argc,argv[1])) break; sleep(2); } // 10 сек
snprintf(path,100,action,'!'); system(path); // подать сигнал что все в порядке
continue;
}
if(r==0x0D || r==0x0A || r=='"' || r=='\'') r='@'; // не надо нам энтеров и кавычек!
snprintf(path,100,action,r);
printf("ACTION: `%s`\n",path);
system(path);
}
}
PS: Что любопытно: говорят, вот эти мелкие USB-карточки, которые имеют на борту кнопки, тоже определяются как устройство USB-клавиатуры. А значит, их можно втыкать во всякие там домашние роутеры и raspberry pi чтобы не только поиметь звуковые оповещения, но и запрограммировать в системе полезные команды по нажатию внешних кнопок, чего у таких устройств вечно не хватает. Но сам я эти звуковушки пока не тестировал, заказал на Алиэкспрессе сейчас (63 руб), как дойдет - потестирую.
это перепост заметки, оригинал находится на моем сайте: http://lleo.me/dnevnik/2018/12/26_keyboard.html