lleo (lleo) wrote,
lleo
lleo

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==-&& 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)<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>=0ioctl(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 0x45r=0; break;
    
// 000 -> 0 0 0          // 000 -> --- NO ---
    
case 0x52r='0'; break; case 0x6Er='0'; break;
    case 
0x4fr='1'; break; case 0x6Br='1'; break;
    case 
0x50r='2'; break; case 0x6Cr='2'; break;
    case 
0x51r='3'; break; case 0x6Dr='3'; break;
    case 
0x4br='4'; break; case 0x69r='4'; break;
    case 
0x4cr='5'; break; //--- NO ---
    
case 0x4dr='6'; break; case 0x6Ar='6'; break;
    case 
0x47r='7'; break; case 0x66r='7'; break;
    case 
0x48r='8'; break; case 0x67r='8'; break;
    case 
0x49r='9'; break; case 0x68r='9'; break;
    case 
0x53r='.'; break; case 0x6Fr='.'; break;
        case 
0x60r='E'; break; // Enter
        
case 0x62r='C'; break; // /
        
case 0x37r='Z'; break; // *
        
case 0x4ar='M'; break; // -
        
case 0x4er='P'; break; // +
        
case 0x0Er='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) {
    
read(fd, &evsizeof (struct input_event));
    if(
== (ssize_t)-1) {
        if (
errno == EINTR) continue;
        else break;
    } else if(
!= 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,chararg) {
    
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(fdEVIOCGNAME (sizeof (name)), name);
    
fprintf(stderr"Reading from: %s (%s)\n"pathname);
    return 
0;
}


int main(int argccharargv[]) {

    
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>2snprintf(action,1024,"%s",argv[2]); // скопировать строку акции

    // коннектимся
    
while(1) { if(== 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(== 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
Tags: #define, #include, домашний сервер, программирование, сделай сам
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments