Если ваша клавиатура не распознаётся системой как HID-устройство (Human Interface Device) или требует нестандартной обработки нажатий — например, для игровых макросов, мультимедийных кнопок или кастомных раскладок — стандартные драйверы Windows/Linux не подойдут. В 90% таких случаев проблема решается написанием собственного драйвера, который перехватывает IRP-пакеты (I/O Request Packet) или эмулирует ввод через виртуальное устройство. Эта статья покрывает весь цикл: от анализа USB-дескрипторов клавиатуры до компиляции signed-драйвера для 64-битных ОС.
Мы не будем рассматривать тривиальные случаи вроде смены раскладки через SetWindowsHookEx или перехвата клавиш на уровне пользовательского пространства (user-mode). Речь пойдёт о ядре ОС: написании Kernel-Mode Driver (KMDF) для Windows или модуля ядра (input.ko) для Linux. Примером актуальной задачи может служить поддержка клавиатур с нестандартными Usage Page (например, Elgato Stream Deck в режиме клавиатуры) или восстановление работы устройства после обновления ОС, "сломавшего" родные драйверы.
1. Когда нужен кастомный драйвер клавиатуры
Прежде чем погружаться в разработку, проверьте, не решается ли ваша задача более простыми методами. Создание драйвера оправдано только в трёх случаях:
- 🔌 Клавиатура не определяется системой (отсутствует в
Диспетчере устройствили определяется как "Неизвестное устройство"). Причина — некорректные VID/PID или отсутствие стандартного HID-дескриптора. - 🎮 Требуется переопределение скан-кодов на уровне ядра (например, для игровых клавиатур с макросами, где стандартные утилиты вроде AutoHotkey не подходят из-за задержек).
- 🔒 Нужна защита от кейлоггеров путём виртуализации ввода (драйвер создаёт виртуальную клавиатуру, а физическая отключается от системы).
Во всех остальных случаях (переназначение клавиш, макросы, подсветка) хватит пользовательских утилит. Например, для Windows подойдёт SharpKeys или PowerToys, для Linux — xmodmap или evdev.
⚠️ Внимание: Драйвер ядра — это код, выполняемый с максимальными привилегиями. Ошибка в нём может привести к BSOD (Windows) или kernel panic (Linux). Все тесты проводите на виртуальной машине или резервном ПК.
2. Архитектура драйвера клавиатуры: ключевые компоненты
Драйвер клавиатуры взаимодействует с тремя слоями системы:
- Аппаратный уровень: считывание данных с USB/HID-устройства через
USB-стек(в Windows — UCX, в Linux — usbcore). - Ядро ОС: обработка прерываний (IRQ) и формирование событий ввода (в Windows —
Kbdclass, в Linux —input subsystem). - Пользовательский уровень: передача данных приложениям через
Win32 API(ReadFile, WriteFile) или/dev/input/event*в Linux.
Структура минимального драйвера для Windows (KMDF) включает:
- 📜 Инф-файл (
.inf) — описывает параметры установки и совместимость с устройствами. - 🖥️ DriverEntry — точка входа, где регистрируются callback-функции для обработки
IRP_MJ_READ/IRP_MJ_WRITE. - 🔌 EvtDeviceAdd — вызывается при подключении устройства, здесь настраивается HID-минипорта.
- 🔄 EvtInterruptDpc — обработчик прерываний от клавиатуры (декодирует скан-коды).
| Компонент | Windows (KMDF) | Linux (модуль ядра) |
|---|---|---|
| Точка входа | DriverEntry | module_init |
| Обработка прерываний | EvtInterruptDpc | irq_handler_t |
| Регистрация устройства | WdfDeviceCreate | input_register_device |
| Чтение данных | WdfRequestSend | input_event |
| Отладка | DbgPrint + WinDbg | printk + dmesg |
3. Подготовка среды разработки
Минимальные требования для начала работы:
- 🪟 Для Windows:
- Visual Studio 2022 с компонентом Desktop development with C++.
- Windows Driver Kit (WDK) для вашей версии ОС (скачивается через Visual Studio Installer).
- Виртуальная машина с Windows 10/11 (для тестов).
- Сертификат для подписи драйвера (можно сгенерировать тестовый через
MakeCert).
- 🐧 Для Linux:
- Ядро версии 5.x+ (проверьте через
uname -r). - Заголовочные файлы ядра (
linux-headers-$(uname -r)). - Утилиты
make,gcc,dkms. - Права
rootдля загрузки модуля (insmod).
- Ядро версии 5.x+ (проверьте через
Для анализа трафика клавиатуры понадобятся:
- USBlyzer (Windows) или Wireshark с фильтром
usb. - HIDDescriptorTool (для парсинга дескрипторов).
- USBView (просмотр топологии USB-устройств).
⚠️ Внимание: В Windows x64 все драйверы должны быть подписаны доверенным сертификатом. Для тестов можно отключить проверку подписи через bcdedit /set testsigning on, но это сделает систему уязвимой для вредоносного ПО.
Установить Visual Studio + WDK (Windows) или заголовки ядра (Linux)|Скачать утилиты для анализа USB-трафика (USBlyzer, Wireshark)|Создать виртуальную машину для тестов|Подготовить тестовый сертификат для подписи (Windows)|Получить дамп дескрипторов вашей клавиатуры-->
4. Анализ HID-дескрипторов клавиатуры
Любая клавиатура, подключённая по USB, передаёт данные в формате HID-протокола. Чтобы драйвер корректно её распознал, нужно:
- Получить дескрипторы устройства:
# Linuxlsusb -v -d VID:PID | grep -A 20 "Config Descriptor"
Windows (через USBView)
Откройте устройство → вкладка "Descriptors"
- Найти
HID Descriptor(обычно имеет тип0x21). В нём указаны:bcdHID— версия протокола (например,1.11).bCountryCode— код страны (важен для раскладок).bNumDescriptors— количество дополнительных дескрипторов.
Report Descriptor — он описывает формат пакетов данных (какие байты отвечают за модификаторы, скан-коды и т.д.).Пример Report Descriptor для стандартной клавиатуры:
05 01 // Usage Page (Generic Desktop)
09 06 // Usage (Keyboard)
A1 01 // Collection (Application)
05 07 // Usage Page (Key Codes)
19 E0 // Usage Minimum (Left Control)
19 E7 // Usage Maximum (Right GUI)
15 00 // Logical Minimum (0)
25 01 // Logical Maximum (1)
75 01 // Report Size (1 bit)
95 08 // Report Count (8 bits)
81 02 // Input (Data, Variable, Absolute)
...
Если ваша клавиатура использует нестандартные Usage Page (например, 0xFF00 для мультимедийных кнопок), их придётся обрабатывать вручную в драйвере.
Пример дескриптора для клавиатуры с мультимедийными кнопками
05 0C // Usage Page (Consumer)
09 01 // Usage (Consumer Control)
A1 01 // Collection (Application)
05 0C // Usage Page (Consumer)
09 CD // Usage (Play/Pause)
09 B5 // Usage (Scan Next Track)
09 B6 // Usage (Scan Previous Track)
15 00 // Logical Minimum (0)
25 01 // Logical Maximum (1)
75 01 // Report Size (1)
95 03 // Report Count (3)
81 02 // Input (Data, Variable, Absolute)
C0 // End Collection
5. Пишем драйвер для Windows (KMDF)
Рассмотрим минимальный рабочий пример драйвера, который перехватывает нажатия клавиш и логирует их в Debug View.
Шаг 1: Создание проекта
В Visual Studio выберите File → New → Project → Kernel Mode Driver (KMDF). В настройках проекта укажите:
- Target OS:
Windows 10(или ваша версия). - Driver Type:
Function Driver. - Use WDF:
Yes(Windows Driver Framework).
Шаг 2: Код драйвера
Основные файлы:
Driver.c— точка входа и обработчики событий.Driver.h— заголовки и структуры.YourKeyboard.inf— инф-файл для установки.
Пример кода для обработки прерывания от клавиатуры:
NTSTATUS
EvtInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFDEVICE Device
)
{
UCHAR buffer[8] = {0};
WDF_REQUEST_READ Options;
WDF_REQUEST_READ_BUFFER(Options, buffer, sizeof(buffer));
// Читаем данные из устройства
WdfRequestSend(WdfInterruptGetRequest(Interrupt), WdfDeviceGetIoTarget(Device), &Options);
// Логируем скан-код (например, первый байт — модификаторы, второй — ключ)
DbgPrint("Keyboard scan code: 0x%02X\n", buffer[2]);
return STATUS_SUCCESS;
}
Шаг 3: Компиляция и установка
После сборки (Ctrl+Shift+B) получите файлы:
YourKeyboard.sys— сам драйвер.YourKeyboard.inf— инф-файл.YourKeyboard.cat— каталог безопасности.
Для установки:
- Подключите клавиатуру к тестовой машине.
- Откройте
Диспетчер устройств, найдите клавиатуру (возможно, в разделе "Другие устройства"). - Обновите драйвер вручную, указав путь к
.inf-файлу. - Проверьте логи в DebugView (фильтр по
Keyboard scan code).
6. Пишем драйвер для Linux
В Linux процесс проще благодаря открытой архитектуре ядра. Мы создадим модуль, который регистрирует виртуальную клавиатуру и эмулирует нажатия.
Шаг 1: Создание заготовки модуля
Сгенерируйте шаблон:
# Создайте файл mykeyboard.c
#include
#include
#include
static struct input_dev *my_keyboard;
static int __init mykeyboard_init(void)
{
// Регистрация виртуального устройства
my_keyboard = input_allocate_device();
if (!my_keyboard)
return -ENOMEM;
// Настройка свойств
my_keyboard->name = "My Virtual Keyboard";
my_keyboard->id.bustype = BUS_USB;
my_keyboard->id.vendor = 0x1234;
my_keyboard->id.product = 0x5678;
// Установка поддерживаемых событий
__set_bit(EV_KEY, my_keyboard->evbit);
__set_bit(KEY_A, my_keyboard->keybit); // Пример: клавиша 'A'
// Регистрация в системе
if (input_register_device(my_keyboard)) {
input_free_device(my_keyboard);
return -EFAULT;
}
printk(KERN_INFO "Virtual keyboard registered\n");
return 0;
}
static void __exit mykeyboard_exit(void)
{
input_unregister_device(my_keyboard);
printk(KERN_INFO "Virtual keyboard unregistered\n");
}
module_init(mykeyboard_init);
module_exit(mykeyboard_exit);
MODULE_LICENSE("GPL");
Шаг 2: Компиляция и загрузка
Соберите модуль:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
sudo insmod mykeyboard.ko
Проверьте появление устройства:
ls /dev/input/by-id/ | grep "My Virtual Keyboard"
cat /proc/bus/input/devices
Шаг 3: Эмуляция нажатий
Чтобы сэгмулировать нажатие клавиши KEY_A:
input_report_key(my_keyboard, KEY_A, 1); // Нажатие
input_sync(my_keyboard);
input_report_key(my_keyboard, KEY_A, 0); // Отпускание
input_sync(my_keyboard);
Для взаимодействия с физической клавиатурой потребуется работать с usb_hid или hidraw. Пример чтения данных:
int fd = open("/dev/hidraw0", O_RDONLY);
unsigned char buf[8];
read(fd, buf, sizeof(buf));
// buf[0] — модификаторы, buf[2] — скан-код
Как найти hidraw-устройство для клавиатуры
1. Подключите клавиатуру.
2. Выполните ls /dev/hidraw* до и после подключения.
3. Проверьте права доступа: sudo chmod 666 /dev/hidrawX.
7. Типичные ошибки и их решения
Даже опытные разработчики сталкиваются с проблемами при написании драйверов. Вот самые распространённые:
| Ошибка | Причина | Решение |
|---|---|---|
Драйвер не загружается (STATUS_INVALID_IMAGE_FORMAT) |
Несовместимость разрядности (x86 vs x64) или отсутствие подписи. | Соберите драйвер для правильной архитектуры. Подпишите его или отключите проверку (bcdedit /set nointegritychecks on). |
| Клавиатура не реагирует на нажатия | Неверно обработан Report Descriptor или не настроены evbit/keybit. |
Проверьте дескрипторы через HIDDescriptorTool. Убедитесь, что скан-коды сопоставлены правильно. |
BSOD с кодом IRQL_NOT_LESS_OR_EQUAL |
Доступ к памяти с неправильным уровнем IRQL (например, вызов ExAllocatePool в обработчике прерывания). |
Используйте WdfMemoryCreate с флагом NonPagedPoolNx. Перенесите тяжелые операции в Work Item. |
| Драйвер загружается, но устройство не появляется в системе | Неверные VID/PID в .inf-файле или ошибка в EvtDeviceAdd. |
Проверьте логи установки (setupapi.dev.log в Windows). Используйте USBView для сравнения VID/PID. |
В Linux модуль не компилируется (unknown type name ‘input_dev’) |
Отсутствуют заголовки ядра или неверная версия. | Установите linux-headers-$(uname -r). Проверьте совместимость версии ядра и модуля. |
⚠️ Внимание: Если драйвер вызывает BSOD в Windows, система может заблокировать его загрузку. Чтобы разблокировать, загрузитесь в Safe Mode и удалите драйвер через pnputil /delete-driver.
8. Тестирование и отладка
Отладка драйверов требует специальных инструментов. Вот минимальный набор:
- 🪲 Windows:
- WinDbg + Kernel Debugging (подключение по
COMилиUSB). - DebugView для просмотра выводов
DbgPrint. - Driver Verifier (
verifier.exe) — выявляет утечки памяти и race conditions.
- WinDbg + Kernel Debugging (подключение по
- 🐧 Linux:
dmesg | tail— логи ядра.straceдля отслеживания системных вызовов.ftrace— трассировка функций ядра.
Пример отладки через WinDbg:
- Подключите две машины: host (с WinDbg) и target (тестовая).
- На target выполните:
bcdedit /debug onbcdedit /dbgsettings net hostip:192.168.1.100 port:50000 key:1.2.3.4
- На host запустите WinDbg и подключитесь:
File → Kernel Debug → NetIP: 192.168.1.100, Port: 50000
- Установите точку останова на вашу функцию:
bp YourDriver!EvtInterruptDpc
Для тестирования клавиатуры в Linux используйте:
# Просмотр событий ввода
sudo evtest /dev/input/eventX
Тест виртуальной клавиатуры
sudo input-events 0 # Нажмите клавишу на виртуальном устройстве
FAQ: Частые вопросы по написанию драйверов клавиатуры
Можно ли написать драйвер клавиатуры на Python?
Нет. Драйверы ядра требуют низкоуровневого доступа к оборудованию, что возможно только на C/C++ (Windows) или C (Linux). Однако вы можете написать пользовательскую утилиту на Python, которая взаимодействует с драйвером через IOCTL (Windows) или /dev/input/event* (Linux).
Как обойти проверку подписи драйвера в Windows x64?
Есть три варианта:
- Отключить проверку подписи (
bcdedit /set testsigning on). Небезопасно! - Купить сертификат EV Code Signing (от ~$200/год).
- Использовать DSE (Driver Signature Enforcement) Override (работает только на некоторых версиях Windows 10).
Для тестов лучше использовать виртуальную машину с отключённой проверкой.
Можно ли переназначить клавиши без написания драйвера?
Да, в большинстве случаев. Используйте:
- 🪟 Windows: AutoHotkey, SharpKeys, PowerToys Keyboard Manager.
- 🐧 Linux:
xmodmap,setkeycodes,evdev. - 🍎 macOS: Karabiner-Elements.
Драйвер нужен только если требуется работа на уровне ядра (например, для античитов в играх).
Как узнать VID и PID своей клавиатуры?
Способы для разных ОС:
- 🪟 Windows:
- Откройте
Диспетчер устройств. - Найдите клавиатуру →
Свойства → Сведения → ИД оборудования. - Формат:
VID_XXXX&PID_XXXX.
- Откройте
lsusb | grep -i keyboard
или
dmesg | grep -i usb
Где взять исходники готовых драйверов для примеров?
Рекомендуемые репозитории:
- 🔗 Официальные примеры от Microsoft (включая KMDF и HID).
- 🔗 Драйверы HID в ядре Linux.
- 🔗 libwdi — утилита для создания драйверов USB-устройств.