|
|
Linux: перехват клавиатуры приложением
это перепост заметки, оригинал находится на моем сайте: http://lleo.me/dnevnik/2018/12/26_keyboard.html С 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/111.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
|
|