Войти в систему

Home
    - Создать дневник
    - Написать в дневник
       - Подробный режим

LJ.Rossia.org
    - Новости сайта
    - Общие настройки
    - Sitemap
    - Оплата
    - ljr-fif

Редактировать...
    - Настройки
    - Список друзей
    - Дневник
    - Картинки
    - Пароль
    - Вид дневника

Сообщества

Настроить S2

Помощь
    - Забыли пароль?
    - FAQ
    - Тех. поддержка



Пишет Леонид Каганов ([info]lleokaganov)
@ 2018-12-27 05:36:00


Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:домашний сервер, программирование, сделай сам

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