2025, Oct 03 09:18
Почему ввод Ctrl+Shift+U 0985 даёт u0985 и как исправить в evdev/uinput
Почему при эмуляции Ctrl+Shift+U в Linux через evdev/uinput вводится u0985 вместо символа Unicode. Как это исправить: объявить модификаторы в возможностях клавиатуры.
Переназначить физическую клавишу так, чтобы через виртуальную клавиатуру вводился другой символ Unicode, на evdev и uinput кажется задачей простой — пока поле ввода упрямо не показывает буквальный “u0985” вместо символа по этому коду. Если вы эмулируете Ctrl+Shift+U, затем набираете шестнадцатеричные цифры, но целевое приложение принимает обычные буквы и цифры, чаще всего дело не в самой последовательности.
Постановка задачи
Цель — нажать реальную клавишу, перехватить её и заставить виртуальную клавиатуру внедрить последовательность для ввода Unicode Ctrl+Shift+U 0985 Enter, чтобы активное приложение получило сам символ, а не сырой текст “u0985”. Наблюдаемое поведение: в фокусируемом элементе появляется “u0985”, а не соответствующий символ Unicode.
Воспроизводимый код, демонстрирующий проблему
Пример ниже читает события с физического устройства ввода, захватывает его и через виртуальное устройство uinput отправляет Ctrl+Shift+U, цифры 0, 9, 8, 5 и Enter. Виртуальное устройство объявляет ограниченный набор клавиш в своих возможностях — и именно здесь скрыта тонкая ошибка.
import evdev
from evdev import UInput, ecodes as codes
import time
# Подключаемся к физическому устройству, похожему на клавиатуру
src_dev = evdev.InputDevice('/dev/input/event2')
src_dev.grab()
# Виртуальное устройство с неполным набором возможностей (проблема)
virt_caps = {codes.EV_KEY: [
    codes.KEY_A,
    codes.KEY_B,
    codes.KEY_G,
    codes.KEY_U,
    codes.KEY_0,
    codes.KEY_9,
    codes.KEY_8,
    codes.KEY_5,
    codes.KEY_ENTER,
]}
vkb = UInput(virt_caps, name='virtual_kbd_demo')
for evt in src_dev.read_loop():
    if evt.type == codes.EV_KEY:
        key_info = evdev.categorize(evt)
        if key_info.keystate == evdev.KeyEvent.key_down:
            # Запускаем режим ввода Unicode
            vkb.write(evt.type, codes.KEY_LEFTCTRL, 1)
            vkb.write(evt.type, codes.KEY_LEFTSHIFT, 1)
            vkb.write(evt.type, codes.KEY_U, 1)
            vkb.write(evt.type, codes.KEY_U, 0)
            vkb.write(evt.type, codes.KEY_LEFTSHIFT, 0)
            vkb.write(evt.type, codes.KEY_LEFTCTRL, 0)
            vkb.syn()
            time.sleep(0.1)
            # Отправляем шестнадцатеричные цифры
            vkb.write(evt.type, codes.KEY_0, 1)
            vkb.write(evt.type, codes.KEY_0, 0)
            vkb.write(evt.type, codes.KEY_9, 1)
            vkb.write(evt.type, codes.KEY_9, 0)
            vkb.write(evt.type, codes.KEY_8, 1)
            vkb.write(evt.type, codes.KEY_8, 0)
            vkb.write(evt.type, codes.KEY_5, 1)
            vkb.write(evt.type, codes.KEY_5, 0)
            vkb.syn()
            time.sleep(0.1)
            # Подтверждаем Enter
            vkb.write(evt.type, codes.KEY_ENTER, 1)
            vkb.write(evt.type, codes.KEY_ENTER, 0)
            vkb.syn()
        elif key_info.keystate == evdev.KeyEvent.key_up:
            pass
    else:
        # Пробрасываем не-клавишные события, чтобы сохранить тайминг/последовательность
        vkb.write(evt.type, evt.code, evt.value)
# Завершение работы
src_dev.ungrab()
vkb.close()
Что происходит на самом деле
Последовательность вводится верно, но виртуальное устройство не заявляет поддержку модификаторов, которые пытается сгенерировать. Если создать uinput‑устройство без KEY_LEFTCTRL и KEY_LEFTSHIFT в списке возможностей, нажатия этих клавиш от него не принимаются. В итоге система видит только “u” и цифры — отсюда и буквальный текст “u0985” в фокусируемом элементе вместо перехода в режим Unicode и составления целевого символа.
Решение
Объявите каждую клавишу, которую виртуальное устройство когда‑либо будет отправлять. В нашем случае добавьте KEY_LEFTCTRL и KEY_LEFTSHIFT в набор возможностей EV_KEY. После этого та же логика инъекции даёт ожидаемый символ Unicode.
import evdev
from evdev import UInput, ecodes as codes
import time
# Подключаемся к физическому устройству, похожему на клавиатуру
src_dev = evdev.InputDevice('/dev/input/event2')
src_dev.grab()
# Виртуальное устройство с полным набором возможностей для этой последовательности
virt_caps = {codes.EV_KEY: [
    codes.KEY_LEFTCTRL,   # добавлено
    codes.KEY_LEFTSHIFT,  # добавлено
    codes.KEY_U,
    codes.KEY_0,
    codes.KEY_9,
    codes.KEY_8,
    codes.KEY_5,
    codes.KEY_ENTER,
]}
vkb = UInput(virt_caps, name='virtual_kbd_demo')
for evt in src_dev.read_loop():
    if evt.type == codes.EV_KEY:
        key_info = evdev.categorize(evt)
        if key_info.keystate == evdev.KeyEvent.key_down:
            # Запускаем режим ввода Unicode
            vkb.write(evt.type, codes.KEY_LEFTCTRL, 1)
            vkb.write(evt.type, codes.KEY_LEFTSHIFT, 1)
            vkb.write(evt.type, codes.KEY_U, 1)
            vkb.write(evt.type, codes.KEY_U, 0)
            vkb.write(evt.type, codes.KEY_LEFTSHIFT, 0)
            vkb.write(evt.type, codes.KEY_LEFTCTRL, 0)
            vkb.syn()
            time.sleep(0.1)
            # Отправляем шестнадцатеричные цифры
            vkb.write(evt.type, codes.KEY_0, 1)
            vkb.write(evt.type, codes.KEY_0, 0)
            vkb.write(evt.type, codes.KEY_9, 1)
            vkb.write(evt.type, codes.KEY_9, 0)
            vkb.write(evt.type, codes.KEY_8, 1)
            vkb.write(evt.type, codes.KEY_8, 0)
            vkb.write(evt.type, codes.KEY_5, 1)
            vkb.write(evt.type, codes.KEY_5, 0)
            vkb.syn()
            time.sleep(0.1)
            # Подтверждаем Enter
            vkb.write(evt.type, codes.KEY_ENTER, 1)
            vkb.write(evt.type, codes.KEY_ENTER, 0)
            vkb.syn()
        elif key_info.keystate == evdev.KeyEvent.key_up:
            pass
    else:
        # Пробрасываем не-клавишные события, чтобы сохранить тайминг/последовательность
        vkb.write(evt.type, evt.code, evt.value)
# Завершение работы
src_dev.ungrab()
vkb.close()
Почему это важно
При создании виртуальных клавиатур или ремапперов на uinput набор возможностей — это своего рода контракт. Стек ввода опирается на него, чтобы решать, какие события устройство вправе генерировать. Если клавиша не объявлена, соответствующее событие могут проигнорировать или некорректно обработать верхние слои, что и приводит к сбивающим с толку результатам вроде буквального “u0985” вместо нужного символа. Эту деталь легко упустить: остальная часть последовательности выглядит правильной, да и отладчики показывают, что вызываются нужные функции.
Выводы
Если собираетесь отправлять какую‑то клавишу с виртуального устройства, заранее внесите её в список возможностей EV_KEY. Это особенно касается модификаторов вроде Ctrl и Shift, которые запускают такие режимы, как ввод Unicode. Как только в наборе возможностей окажутся все используемые клавиши, механизм составления символов работает как задумано, и активное приложение получает сам символ, а не сырые нажатия.