четверг, 14 мая 2020 г.

Эмулятор мыши на Arduino

В свете удаленной работы, на все VDI в офисе расставили агента, определяющего активность пользователя. Первая мысль, которая пришла в голову - сделать имитатор мыши, благо в RDP или ICA сессии невозможно определить откуда именно пришли перемещения.

Программных реализаций мышкодвигателей - полно, так что в этот раз мне захотелось реализовать именно аппаратное решение.

Мысль была следующая. Arduino и подобные платы разработки видятся системой как USB to COM интерфейс. А в Windows, включая Windows 10, до сих пор присутствует поддержка Serial Mouse. Поэтому ничего сложного не составит сэмулировать подобную мышь без какой-либо пайки, а только штатными средствами имеющейся в наличии платы.

Протокол у мыши примитивный, есть готовый пример на Хабре. В примере, кстати, ошибка, которая проявляется эффектом, описанным самим автором:
Отрицательные приращения работают криво, движение на -1 выглядит примерно как на +30 (по амплитуде, с направлением все в порядке).
В коде функции formPacket
C 0.05 KB
  1.     packet[1] = x & 0x1F;  // ошибка, должно быть в обоих местах 0x3F
  2.     packet[2] = y & 0x1F;  

необходимо поменять 0x1F на 0x3F, так как передаются нижние 6 бит, а не 5.

В качестве Arduino я взял имевшийся в загашнике Arduino Nano v3. Свой код привожу ниже.
C 0.78 KB
  1. uint8_t packet[3];
  2.  
  3. static void formPacket(bool leftClick, bool rightClick, uint8_t x, uint8_t y)
  4. {
  5.   packet[0] = (1<<6) | (leftClick<<5) | (rightClick<<4);
  6.   packet[0] |= ((y>>6) & 0x3) << 2;
  7.   packet[0] |= ((x>>6) & 0x3);
  8.   packet[1] = x & 0x3F;
  9.   packet[2] = y & 0x3F;
  10. }

  11. void setup() {
  12.   Serial.begin(1200);
  13.   for(int i=0; i<200; i++)
  14.     Serial.write('M');
  15. }
  16.  
  17. int count=256;
  18. int pos=0;
  19. int d[][2]={
  20.   {1,0},
  21.   {0,1},
  22.   {-1,0},
  23.   {0,-1},
  24.   {0,0},  
  25. };
  26.  
  27. int Speed=8;
  28.  
  29. void loop() {
  30.   while(1)
  31.   {  
  32.     formPacket(0, 0, d[pos][0]*Speed, d[pos][1]*Speed);  
  33.     Serial.write(packet,3);
  34.     count-=Speed;
  35.     if(count<0)
  36.     {
  37.       count=256;
  38.       pos++;
  39.       if(d[pos][0]==0 && d[pos][1]==0)
  40.         pos=0;
  41.     }
  42.     Serial.flush();
  43.     delay(25);
  44.   }
  45. }

Согласно мышиному протоколу, общение с хостом идет на скорости последовательного порта 1200 бит/сек, 7 битов данных, 2 стоп бита. Пойдет и "классический" режим 8 битов данных и 1 стоп, как в моем коде.

Так как COM порт не является Plug and Play устройством, в диспетчере устройств Windows необходимо явно нажать на "Scan for Hardware Changes":
Хост в момент детекта устройства производит определенные манипуляции с линиями DTR и RTS, что в нашем случае приводит к резету Arduino Nano, и, соответственно, запуску функции setup(). Согласно мышиному протоколу, при этом необходимо отправить латинскую букву 'M' (я на всякий случай делаю это 200 раз), и в системе появляется "Microsoft Serial Mouse". На скриншоте также виден COM4 от Arduino, через который и идет "общение" с псевдо-мышью.

Далее - код функции loop() отправляет в цикле дельты перемещения курсора согласно массиву d[][2] - в данном случае это квадрат.
Так как "классические" Serial Mouse не работали со скоростью выше 200 перемещений в секунду, в код добавлена принудительная delay(25). Если ее убрать - курсор будет двигаться по экрану рывками.

Собственно, на этом - всё.
Далее по желанию можно добавить имитацию нажатий на кнопки или какую-нибудь логику. Прокрутку колеса, увы, добавить не получится - в те времена подобных мышей просто не существовало и протокол колесо не поддерживает. Я пробовал посылать четвертый байт с разными значениями - однако, никакой реакции на него от операционной системы не заметил.