Blog. Just Blog

Как получить список устройств ввода

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Как получить список устройств ввода
Как получить список устройств ввода

Как узнать, подключена ли к компьютеру клавиатура и/или мышь? Ответить на этот вопрос можно путем перечисления всех подключенных системных устройств ввода при помощи функции GetRawInputDeviceList. Она позволяет получить список идентификаторов и типов устройств ввода, а на основании идентификатора при помощи функции GetRawInputDeviceInfo можно узнать об этом устройстве более детальную информацию.

Начинаем, как водится, с описания необходимых для работы структур и констант. Структура RID_DEVICE_INFO используется для всех типов устройств, просто для каждого типа в ней будет заполнен свой набор полей.
  1. struct RAWINPUTDEVICELIST
  2.     hDevice dd ?
  3.     dwType  dd ?
  4. ends
  5.  
  6. struct RID_DEVICE_INFO_MOUSE
  7.     dwId                dd ?
  8.     dwNumberOfButtons   dd ?
  9.     dwSampleRate        dd ?
  10.     fHasHorizontalWheel dd ?
  11. ends
  12.  
  13. struct RID_DEVICE_INFO_KEYBOARD
  14.     dwType                 dd ?
  15.     dwSubType              dd ?
  16.     dwKeyboardMode         dd ?
  17.     dwNumberOfFunctionKeys dd ?
  18.     dwNumberOfIndicators   dd ?
  19.     dwNumberOfKeysTotal    dd ?
  20. ends
  21.  
  22. struct RID_DEVICE_INFO_HID
  23.     dwVendorId      dd ?
  24.     dwProductId     dd ?
  25.     dwVersionNumber dd ?
  26.     usUsagePage     dw ?
  27.     usUsage         dw ?
  28. ends
  29.  
  30. struct RID_DEVICE_INFO
  31.     cbSize dd ?
  32.     dwType dd ?
  33.     union
  34.         mouse    RID_DEVICE_INFO_MOUSE
  35.         keyboard RID_DEVICE_INFO_KEYBOARD
  36.         hid      RID_DEVICE_INFO_HID
  37.     ends
  38. ends
  39.  
  40. RIM_TYPEMOUSE    = 0
  41. RIM_TYPEKEYBOARD = 1
  42. RIM_TYPEHID      = 2
  43.  
  44. RIDI_DEVICENAME = 0x20000007
  45. RIDI_DEVICEINFO = 0x2000000b
Теперь надо получить количество подключенных устройств, зарезервировать под них память и загрузить список их идентификаторов и типов. После этого поочередно перебираем каждое устройство из списка и получаем его свойства. В зависимости от переданного параметра, GetRawInputDeviceInfo возвращает тот или иной тип информации об устройстве. Это может быть системное имя устройства или его физические параметры.
  1.         ; Получить количество устройств ввода
  2.         invoke  GetRawInputDeviceList,NULL,numDev,sizeof.RAWINPUTDEVICELIST
  3.         or      eax,eax
  4.         jnz     .no_devices
  5.  
  6.         ; Получить список устройств ввода
  7.         invoke  GetRawInputDeviceList,ridl,numDev,sizeof.RAWINPUTDEVICELIST
  8.  
  9.         mov     esi,ridl
  10.         mov     ebx,[numDev]
  11. .loc_loop:
  12.         ; RAWINPUTDEVICELIST.hDevice
  13.         lodsd
  14.         push    eax
  15.         ; Имя устройства
  16.         mov     [pcbSize],200h
  17.         invoke  GetRawInputDeviceInfo,eax,RIDI_DEVICENAME,devname,pcbSize
  18.         pop     eax
  19.         ; Информация об устройстве
  20.         mov     [pcbSize],sizeof.RID_DEVICE_INFO
  21.         invoke  GetRawInputDeviceInfo,eax,RIDI_DEVICEINFO,riddi,pcbSize
  22.  
  23.         ; Mouse
  24.         cmp     [riddi.dwType],RIM_TYPEMOUSE
  25.         jne     @f
  26.         ...
  27.         ...
  28.         jmp     .loc_next
  29. @@:
  30.         ; Keyboard
  31.         cmp     [riddi.dwType],RIM_TYPEKEYBOARD
  32.         jne     @f
  33.         ...
  34.         ...
  35.         jmp     .loc_next
  36. @@:
  37.         ; HID
  38.         ...
  39.         ...
  40. .loc_next:
  41.         ; RAWINPUTDEVICELIST.dwType
  42.         lodsd
  43.         ; Следующее устройство
  44.         dec     ebx
  45.         jnz     .loc_loop
На основании значения dwType используется своя структура для мыши, клавиатуры или HID-устройства. Таким образом можно узнать количество кнопок у мыши, наличие у нее горизонтальной прокрутки, количество кнопок у клавиатуры, в том числе отдельно можно узнать количество функциональных клавиш и даже количество светодиодных индикаторов. Буфер ridl для приема данных обязательно должен быть выровнен на границу DWORD, иначе данные не будут получены.

Отлично, список устройств мы получили, но в нем нет человекопонятных названий устройств, только системные имена типа \\?\HID#VID_0D9F&PID_00A6#6&166ed471&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}. Как все-таки получить название устройства, понятное для человека? Сделать это можно, но нам понадобятся дополнительные структуры и константы.
  1. struct SP_DEVINFO_DATA
  2.     cbSize    dd ?
  3.     ClassGuid rb 16
  4.     DevInst   dd ?
  5.     Reserved  dd ?
  6. ends
  7.  
  8. DIGCF_PRESENT      = 0x00000002
  9. DIGCF_ALLCLASSES   = 0x00000004
  10. SPDRP_DEVICEDESC   = 0x00000000
  11. SPDRP_FRIENDLYNAME = 0x0000000C
Теперь давайт посмотрим повнимательнее на системное имя устройства. Оно состоит из нескольких частей, разделенных символом "#":
  • \\?\ или \??\ (для WinXP) - префикс имени
  • HID - класс устройства
  • VID_0D9F&PID_00A6 - VID/PID, то есть VendorID и ProductID - это идентификаторы производителя и устройства, для разных классов они могут формироваться и выглядеть по-разному
  • 6&166ed471&0&0000 - уникальный идентификатор устройства в системе
  • {4d1e55b2-f16f-11cf-88cb-001111000030} - GUID интерфейса устройства
С парсингом строки и извлечением из нее необходимых данных проблем возникнуть не должно. Для каждого устройства в реестре системы есть соответствующая запись, путь к которой формируется из перечисленных частей. Так, для этого устройства ветка реестра будет иметь вид HKLM\SYSTEM\CurrentControlSet\Enum\HID\VID_0D9F&PID_00A6\6&166ed471&0&0000. В ней содержится вся информация об устройстве, включая его описание. Опционально может быть даже человекопонятное название, но это редко.

Данные устройства в реестре
Данные устройства в реестре

Для получения названия устройства можно извлечь данные из реестра, сформировав путь из частей его системного имени, но лучше воспользоваться функциями WinAPI для работы с устройствами.
  1.         ; Преобразовать строку имени устройства из системного вида
  2.         ; в Instance ID типа HID\VID_0D9F&PID_00A6\6&166ed471&0&0000
  3.         mov     esi,devname
  4.         mov     edi,instanceID
  5. @@:
  6.         lodsb
  7.         cmp     al,'\'
  8.         je      @b
  9.         cmp     al,'?'
  10.         je      @b
  11.         dec     esi
  12.  
  13.         ; Тут дополнительно формируется юникодная строка класса устройства
  14.         xor     ecx,ecx
  15.         xor     eax,eax
  16. @@:
  17.         lodsb
  18.         cmp     al,'#'
  19.         je      @f
  20.         stosb
  21.         mov     word [szClass+ecx],ax
  22.         inc     ecx
  23.         inc     ecx
  24.         jmp     @b
  25. @@:
  26.         mov     word [szClass+ecx],0
  27.  
  28.         mov     al,'\'
  29.         stosb
  30. @@:
  31.         lodsb
  32.         cmp     al,'#'
  33.         je      @f
  34.         stosb
  35.         jmp     @b
  36. @@:
  37.         mov     al,'\'
  38.         stosb
  39. @@:
  40.         lodsb
  41.         cmp     al,'#'
  42.         je      @f
  43.         stosb
  44.         jmp     @b
  45. @@:
  46.         mov     al,0
  47.         stosb
  48.  
  49.         ; Получить хэндл списка устройств этого класса
  50.         invoke  SetupDiGetClassDevs,NULL,szClass,\
  51.                 NULL,DIGCF_ALLCLASSES+DIGCF_PRESENT
  52.         mov     [hDevInfo],eax
  53.  
  54.         xor     ebx,ebx
  55. .loc_enum_devs:
  56.         ; Получить информацию о следующем устройстве этого класса
  57.         mov     [pspDevInfoData.cbSize],sizeof.SP_DEVINFO_DATA
  58.         invoke  SetupDiEnumDeviceInfo,[hDevInfo],ebx,pspDevInfoData
  59.         or      eax,eax
  60.         jz      .no_more_devs
  61.         invoke  SetupDiGetDeviceInstanceId,[hDevInfo], pspDevInfoData,\
  62.                 DeviceInstanceId, 100h, tmp
  63.  
  64.         ; InstanceId устройства соответствует нашему устройству?
  65.         invoke  lstrcmpi,instanceID,DeviceInstanceId
  66.         or      eax,eax
  67.         jnz     .loc_next_device
  68.  
  69.         ; Получить человекопонятное название устройства
  70.         invoke  SetupDiGetDeviceRegistryProperty,[hDevInfo],\
  71.                 pspDevInfoData,SPDRP_FRIENDLYNAME,NULL,\
  72.                 PropertyBuffer,100h,tmp
  73.         or      eax,eax
  74.         jnz     .no_more_devs
  75.  
  76.         ; Получить описание устройства
  77.         invoke  SetupDiGetDeviceRegistryProperty,[hDevInfo],\
  78.                 pspDevInfoData,SPDRP_DEVICEDESC,NULL,\
  79.                 PropertyBuffer,100h,tmp
  80.         or      eax,eax
  81.         jnz     @f
  82.         ; Никакого названия не найдено, записать в строку "Unknown"
  83.         invoke  lstrcpy,PropertyBuffer,szUnknown
  84. @@:
  85.         jmp     .no_more_devs
  86.  
  87. .loc_next_device:
  88.         ; Следующее устройство
  89.         inc     ebx
  90.         jmp     .loc_enum_devs
  91.  
  92. .no_more_devs:
  93.         ; Прибраться за собой
  94.         invoke  SetupDiDestroyDeviceInfoList,[hDevInfo]
Получив системное имя устройства при помощи функции GetRawInputDeviceInfo, как описано выше, преобразуем это имя в строку Instance ID, попутно получив название класса устройства. Затем при помощи функции SetupDiGetClassDevs получим хэндл списка всех устройств этого класса. ASNI-версии этой функции нет, поэтому в приведенном примере кода строка имени класса преобразуется в юникодную. Затем по очереди перебираем все устройства этого класса функцией SetupDiEnumDeviceInfo, по полученной записи устройства получаем его Instance ID с помощью функции SetupDiGetDeviceInstanceId. Если полученный Instance ID совпадает со сформированным Instance ID проверяемого устройства, то останется только получить его человекопонятное имя или описание. Это делается функцией SetupDiGetDeviceRegistryProperty с соответствующим параметром. Instance ID могут отличаться по регистру символов, поэтому при сравнении строк надо использовать регистронезависимые функции. Для юникодного варианта разбор строки имени будет чуть попроще:
  1.         mov     esi,devname
  2.         mov     edi,instanceID
  3. @@:
  4.         lodsw
  5.         cmp     ax,'\'
  6.         je      @b
  7.         cmp     ax,'?'
  8.         je      @b
  9.         dec     esi
  10.         dec     esi
  11. @@:
  12.         lodsw
  13.         cmp     ax,'#'
  14.         je      @f
  15.         stosw
  16.         jmp     @b
  17. @@:
  18.         mov     word [edi],0
  19.         ; Тут дополнительно формируется юникодная строка класса устройства
  20.         invoke  lstrcpy,szClass,instanceID
  21.  
  22.         mov     ax,'\'
  23.         stosw
  24. @@:
  25.         lodsw
  26.         cmp     ax,'#'
  27.         je      @f
  28.         stosw
  29.         jmp     @b
  30. @@:
  31.         mov     ax,'\'
  32.         stosw
  33. @@:
  34.         lodsw
  35.         cmp     ax,'#'
  36.         je      @f
  37.         stosw
  38.         jmp     @b
  39. @@:
  40.         mov     ax,0
  41.         stosw
Ну и после обработки группы устройств не забываем прибираться за собой, удалив ненужный хэндл списка функцией SetupDiDestroyDeviceInfoList.

В приложении пример программы с исходным текстом, которая выводит список всех подключенных к компьютеру устройств ввода, их названия или описания, а также некоторые физические характеристики.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Raw.Input.Devices.Demo.zip (4,687 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 2073 | Комментариев: 10

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (06.12.2021 в 21:04):
Как должно быть охуенно жить на свете вообще без мозга, да? Включил защитник windows или чо там у тебя работает вместо головы, и жизнь прекрасна. Выйди в окно, не засоряй собой планету.
dev (06.12.2021 в 20:32):
в архиве вирус Trojan:Win32/Ulthar.A!ml
ManHunter (26.09.2020 в 14:35):
Дополнил статью информацией, как получить человекопонятное название устройства. С удивлением выяснил, что мой бесперебойник тоже является устройством ввода :) Архив обновлен.
ManHunter (22.09.2020 в 12:56):
Также выяснилось, что буфер ridl должен быть выровнен на границу DWORD, в MSDN про это ни слова. Дополнил статью и исходник.
ManHunter (09.09.2020 в 08:21):
Что интересно, WinXP имя устройства выдает как
Mouse: \??\Root#RDP_MOU#0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}
а на Win7 как
Mouse: \\?\Root#RDP_MOU#0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}
то есть на WinXP строка начинается с "\??\", а на Win7 с "\\?\"

Это так, на случай необходимости детекта физических устройств ввода.
ManHunter (08.09.2020 в 14:36):
Добавил в выхлоп примера RAWINPUTDEVICELIST.hDevice
ManHunter (03.07.2020 в 21:52):
Два последних девайса, которые рутовые \\?\Root#RDP_xxx. Соответственно, физическая клавиатура и физическая мышь. У меня примерно похожая картина, только виртуальных поменьше, а железные определяются точно так же от рута.
DRON (03.07.2020 в 21:32):
Вот реальный пример: https://pastebin.com/rjjibR5A
И как тут определить что есть что?
ManHunter (03.07.2020 в 10:43):
Можно же отсечь ненужное по SampleRate для мыши и всяким NumberOfFunctionKeys для клавы.
DRON (03.07.2020 в 09:04):
Всё это, к сожалению, не очень надёжно: игровые мышки видятся как клавы, а игровые клавы как мышки. А у меня даже при физически отключенных мышках/клавах находится пара виртуальных.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.08 сек. / MySQL: 2 (0.0108 сек.) / Память: 4.5 Mb
Наверх