{imgicourl}{zamok}
Другие записи за это число:
2018/12/26 - А вы уже придумали, что дарить друзьям и близким на Новый год?
<< предыдущая заметкаследующая заметка >>
26 декабря 2018
Linux: перехват клавиатуры приложением

С 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 руб), как дойдет - потестирую.

<< предыдущая заметка следующая заметка >>
пожаловаться на эту публикацию администрации портала
архив понравившихся мне ссылок
Оставить комментарий
Windows Safari Chrome
 Москва
0
0
DarkSup
А как-то можно отслеживать нажатие кнопок, или команды подаёшь вслепую, в надежде, что всё сработает как нужно?
Правда, я так понял, под каждой кнопкой подразумевается определённый макрос... тогда, видимо, отсутствие отслеживания вводимых символов не критично, нажал кнопку и ждёшь пока сработает как задумано. Или для некоторых макросов требуется дополнительный ввод параметров? Тогда их, видимо, надо печатать с задержкой в секунду, чтобы попасть в периоды опроса клавиатуры?
Linux Ubuntu Firefox
 Санкт-Петербург
0
0
Leonid Kaganov
Если вы собираетесь с клавиатуры набирать команды для консоли - то зачем вам вся вышеописанная система? Подключайте штатно клавиатуру и набирайте, что хотите.

У меня речь идет об использовании клавиатуры как ряда неких управляющих кнопок. Скажем R - включить принудительную вентиляцию. S - остановить музыку. 0 - поставить квартиру на охрану. Это я для примера, у меня другие задачи, но принцип такой: иногда надо системе подать сигнал, а паять пять кнопок незачем, если есть usb-клавиатура.

"Период опроса" у USB-клавиатуры не существует: это просто поток символов, который устройство дает, а софтинка читает. Не успела прочесть - подождет, прочтет через секунду.
Windows Safari Chrome
 Москва
0
0
DarkSup
Про буфер клавиатуры понятно, тогда действительно удобно получается... а про нажатие нескольких последовательных кнопок я не имел в виду, что нужно вбивать команды, я лишь про передачу параметров в предопределенные функции: например двумя цифрами установить температуру в помещении, или сколько налить воды в ванную. Но если у клавиатуры есть свой буфер, то этот вопрос конечно легко решаем.
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Ох, я бы не стал вслепую такой системой устанавливать уровень воды...

Вообще все-таки установка параметров требует обратной связи. Другая система, другие задачи. Тут речь о сигналах просто.
Windows Safari Chrome
 Москва
0
0
DarkSup
Ну да, я понял, тут задача как в автомобиле: нажал кнопку - получил предопределенный набор реакций.

А эта система у Вас смонтирована на базе обычного бытового роутера? Если мне не изменяет память, Вы писали, что какой-то Асус перепрошили так, что он превратился почти в полноценный сервер с линуксом внутри...
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Асус n16 в Питере: http://spb.lleo.me
А в Чертаново banana bpi r1, она пошустрее и там более чистый линукс: http://home.lleo.me
Windows Safari Chrome
 Санкт-Петербург
0
0
bolshakovdmitry
Надо же, кто-то в 2019 году ещё сидит в интернете через ADSL... не хочу сказать ничего плохого, да и помню про условия квартиросъёма.
Mac Safari
 Москва
0
0
Den_x (#6269400)
Я тоже в МСК на ADSL, мне хватает.
Ценю надежность
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Почему вы решили, что adsl? У меня же onlime выделенка.
Linux Safari Chrome
 Санкт-Петербург
0
0
Tupik
Avangarddsl @ spb
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Ну, может быть. Такова данность квартиры, где я обитаю, я там ничего менять не могу. Господь терпел и нам велел.
Windows Safari Chrome
 Домодедово
0
1
id
> banana bpi r1

...которая имеет такое стадо GPIO, UART и прочих I2C с SPI, что заводить в нее сигналы через USB HID вот как-то... странно, что ли...
Mac Firefox
 Белоруссия
0
0
IgorB
В смысле bluetouth клавиатуру нужно было?
Зачем паять и травить, если вот она мини-клавиатура?
Windows Firefox
 Россия
0
1
Келаврик
#include <stdio.h>
#include <stdlib.h>
...

Урра! Не я один пользуюсь старым добрым с без газа, в смысле без плюсов.
Linux Safari Chrome
 New York
0
0
Кто здесь?
интересно практическое применение данного решения...

А то напоминает системный телефон мини-АТС Порнослоник: если внимательно прочитать инструкцию и запомнить ее - то можно творить чудеса, но обычного человека хватает на то, чтобы тыкать в кнопки "Секретарша" и "Директор".
Linux Ubuntu Firefox
 Санкт-Петербург
0
0
Leonid Kaganov
Ну это если есть некая домашняя система (круглосуточно работающее устройство без монитора), к которой иногда хочется подойти и дать управляющий сигнал. Чего-нибудь там включить, запустить, выключить. Ну там, ксерокс сделать автоматом: взять со сканера скан и кинуть его на принтер. Или включить полив растений вне графика. Или перегрузить сетку WiFi. А ходить браузером долго и неудобно, проще подойти и нажать кнопку.
Linux Ubuntu Firefox
 Германия
0
0
gehrmann
Не суди строго, я не навязываю) Я сам люблю С, но на основании опыта мне думается, что софт такого плана лучше писать на питоне -- очень много ненужных сущностей опускается. А недостатки питона в данной задаче несущественны.
Linux Ubuntu Firefox
 Санкт-Петербург
4
0
Leonid Kaganov
Чувак, я же вроде очень четко описал сферу применения: "софт такого плана" должен висеть в памяти очень ограниченного по ресурсам прибора. У которого может даже 128кб оперативки, а то и меньше. Из которых 1 килобайт отдать не жалко на задачу ожидания событий, которые случаются раз в сутки. Но повесить в память демон на питоне... Хорошо ли ты подумал? ;)))
Windows Firefox
 Москва
0
0
Кирилл
А откуда возникла величина в "128кб", и почему вы думаете, что сможете запихнуть туда Linux?
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Мелкие одноплатные компы - всякие роутера и прочие - там от 128мб начинается оперативки, хотя я слышал про Линуксы на платках с 64мб. У меня в Asus, если не ошибаюсь, 256мб, это немного.
Linux Ubuntu Firefox
 Одесса
0
0
Azimut
ой, ремапы. А список кодов для кнопок обычной клавиатуры дайте пожалуйста, ну т.е. 0..9 понятно, а что ждать от остальных букв ?
ой. без ремапов всё класс. урааа. две клавиатурки в разное принимаются... ша еще каких-нибудь /dev/input/eventххх подсоединить... вместо lirc
Windows Firefox
 Москва
0
0
Johny
Неужели за столько лет нельзя было банальный lirc задействовать?
Linux Safari Chrome
 Санкт-Петербург
0
0
Leonid Kaganov
С какой целью? Бегать по дому, искать, куда дети пульт утащили? Или чтоб само случайно сработало посреди ночи?
Windows Firefox
 Москва
0
0
Johny
для передачи скан-кодов подключеннного USB-HID устройства скрипту /home/lleo/do_commands.php без изобретения собственных велосипедов.
Я вам еще года 3 назад , помниться, здесь же советовал.

Вы список плагинов то смотрели?
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Не смотрел. Не уверен, кстати, что он вообще собран для моего Asus, этот lirc, и ещё вопрос, насколько он день ресурсы и память.
Windows Firefox
 Москва
0
0
Johny
у вас же там openwrt , надеюсь?

приведу цитату из первой попавшейся статьи

В OpenWrt за работу инфракрасного канала связи отвечает программа LIRC
Устанавливаем пакеты:
opkg install kmod-usb-hid
opkg install udev
opkg install lirc
opkg install lircdaemonadd lirctools

При нажатии кнопки на пульте, посылается команда по ИК каналу, соответствующая нажатой кнопке. Приёмник подключенный к usb-порту роутера принимает эту команду и посылает сигнал в LIRC с кодом этой команды. Демон LIRC сравнивает код команды с теми кодами, которые он знает, и выполняет действие, которое сопоставлено этому коду.

P.S. то что у вас USB-HID подключен напрямую по проводу, без ИК-канала, ничего принципиально в этой настройке не меняет.

статья
http://cyber-place.ru/showthread.php?t=367
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Сколько памяти у вас занимает все это в оперативки? Покажите.
Linux Firefox
 Россия
0
0
rar.jpg (#7572084)
Есть уже готовые софтины, запускающие скрипты по нажатию на клавишу, например cmdpad и triggerhappy. Раньше были в стандартных пакетах OpenWRT.
Linux Safari Chrome
 Санкт-Петербург
0
0
Tupik
Но тут же Dd-wrt
Mac Safari
 Израиль
7
0
braintunic
> 1) char path[300];
> 2) char action[300]=...
> 3) snprintf(action,1024,"%s",argv[2]);
> 4) snprintf(path,100,action,'?');

Здесь у тебя ошибка — во всех этих случаях значение размера буфера должно быть идентичным.
Конечно, маловероятно, что эта ошибка реально бабахнет, так как маловероятно, что ты когда либо передашь туда команду длиннее 100 символов (через argv[2]), но теоретически это возможно.

И в таком случае произойдёт вот что:
* если твоя команда длиннее 100 символов, то в строке #4 произойдёт обрезание твоей команды, и последующая операция system выполнит не то, что ты хотел
* если твоя команда длиннее 300 символов, то в строке #3 произойдёт переполнение буфера, и тогда вообще может свалиться что угодно (и будет крайне трудно поймать причину такого сбоя).

По-правильному, здесь надо задать макро (например, #define ACTIONSIZE 256) и использовать это макро во всех этих четырёх строках (и в остальных аналогичных).

Кстати, что это за странные числа “100” и “300”?
Почему не нормальные круглые числа типа “128” и “256”? ;)
Linux Ubuntu Firefox
 De Linge PJ Dronten (The Netherlands)
1
0
Чук
Хм, там ошибков масса, Ну к примеру:
if(argc>2) snprintf(action,1024,"%s",argv[2])
Фактически копирует до 1К из argv[2] в буфер action размером в 300!! байт, который расположен "та-да-ммм!!" на стеке!! Не, если строчки короткие, то проблем не будет. И вообще, зачем там snprintf(), когда есть strncpy() ?
Да и при добавлении в шаблон команды дополнительных аргументов как %c %s %d итп приведёт к занятным эффектам.
Windows Firefox
 Тула
0
0
stream
strncpy? Вот не надо таких советов. У нее есть один милый совершенно неожиданный побочный эффект, про который мало кто знает. Вернее, даже два, просто второй не опасный.

sprintf - абсолютно нормальное решение, просто длину буфера надо правильно указывать, например, snprintf(action, sizeof(action), ....)

Самое правильное решение - strlcpy, но оно не всегда есть из коробки.
Linux Safari Chrome
 Чехия
0
0
atheist
strncpy мучается обнулением всего буфера, если осталось свободное место. а если не осталось, то терминатора не будет вовсе.
Windows Safari Chrome
 Домодедово
0
0
id
Arduino Leonardo/Micro/ все прочие на основе ATmega32u4 великолепно умеют прикидываться USB HID устройствами. Как родные.

"Настоящая" Arduino Uno так тоже умеет (потому что в качестве моста USB-TTLUART там используется целая ATmega U-какая-то), но тут нужен SPI программатор. А с Micro - дело в шляпе сразу.

P.S.
while (1) {
... continue;
... break;
... return;
}
return
...

Не хватает только goto и десятка меток для полной красоты. Советская школа уже потому должна была умереть, что калечила невинные детские души изучением BASIC. Как показала практика - большинство фатально.
Linux Ubuntu Firefox
 Москва
0
0
Leonid Kaganov
Дело как раз в том, что я сейчас избавляюсь от всех самодельных коробочек с Ардуинами, для того USB-клавиатура и была повешена.

А в чем преступность цикла while(1) для программки-демона, который просто по определению не должен завершаться никогда и ни при каком условии?
Windows Safari Chrome
 Домодедово
0
0
id
Преступности-то нет никакой... но через пару лет ты сам-то поймешь, сколько конкретно итераций выполняется цикл while(1), если break из него понатыканы в самых неожиданных местах? а какое, например, значение возвращает функция, насыщенная массой return, в том числе внутри этих самых непонятно сколько раз сработавших циклов?
...В то время как простой переворот условия в if избавляет от необходимости рвать цикл по continue, а вынос логики в while() спасает от неожиданных break.

Мир-то у нас всего один... так почему бы, сотворив какую-то часть этого мира, не сделать это красиво? :)
Linux Safari Chrome
 Москва
0
0
Leonid Kaganov
Может у нас разные представления о красоте? :)
Windows Safari Chrome
 Домодедово
2
0
id
Не исключено :)

В моем представлении у красивой функции только один выход: в конце. А у красивого цикла условие повторяемости задано только в одном месте: в условии оператора цикла. Да, а еще в красивом коде не живут по соседству while(1) и while(true) :)
Linux Safari Chrome
 Москва
1
0
Leonid Kaganov
Мне наоборот нравятся функции именно тем, что там в любой момент можно выскочить. if(!f) return "файл не открывается";
if(f==5) return "доступ запрещен";
И так далее. Очень удобно, читаемо и компактно, рекомендую. Гораздо лучше, чем тянуть безумную структуру до конца, обвешиваясь всякими if() { if() {
if() { }
}
}
Windows Safari Chrome
 Москва
0
0
sibvaleo
этот каммент заставит жестоко страдать Эдсгера Дейкстру и Никлауса Вирта :)
Linux Firefox
 Дмитров
0
0
id59577522
Убил недавно полдня на похожую задачу. Оставлю здесь маленькое наблюдение, может, будет кому-нибудь интересно и не придётся тратить полдня на бисекцию. К роутерам и другим маленьким системкам без udev, systemd прочих усложнений, насколько я понимаю, не относится.

Недавно некто Dmitry Torokhov aka @dtor придумал коммит 1455cf8dbfd06aa7651dcfccbadb7a093944ca65 "driver core: emit uevents when device is bound to a driver" (это хэш в stable-ветке), суть которого состоит в том, что при подключении всяких таких устройств генерируются теперь кроме ADD и REMOVE события BIND и UNBIND. Systemd вплоть до последних версий при получении ADD добавляет device node в нужные места /dev (если это HID, то в /dev/input, но речь как раз об устройствах с кнопками, которые не только HID). Следующим ходом приходит BIND, с которым udev не знает, что делать и на всякий случай device node тут же удаляет. А, может, наоборот, сначала BIND приходит, а потом ADD -- я, подобно udev, разобравшись и починив тут же всё позабыл. Есть миллисекунда или около того, чтобы успеть открыть файл, и всё получилось, кто не успел -- тот опоздал. Выхода два: или не использовать свежие ядра (после 4.12), или откатить вручную этот коммит, или использовать самый последний udev/systemd (я не знаю, какой -- мой дистрибутив использует форк udev -- eudev, там в версии 3.2.5 ещё плохо, а 3.2.1-rc1 -- уже хорошо).

всего комментариев: 41

<< предыдущая заметка следующая заметка >>