Симуляция нажатий Home, End, PgUp, PgDown

    Введение

    image
    Примерно полтора года назад я стал счастливым обладателем HP Mini 110-3155sr. Машинка всем порадовала, но была одна проблема, которая со временем надоедала все больше — отсутствие кнопок Home, End, PgUp, PgDown. О том, как я решил эту проблему при помощи небольшой программки на Python — под катом.

    Решение приходит в голову достаточно быстро. Если нет кнопок — значит, нужно создать для них какие-нибудь хоткеи. Я не нашел, как сделать это системными средствами, поэтому начал перебирать различные программы подобного рода. Несмотря на то, что таких программ действительно много, я не нашел ту, которая мне подошла бы. Одни предлагали выполнение команд на хоткеи, в других вообще не находилось удобного сочетания клавиш.

    В конце концов я решил написать небольшую утилитку своими руками. Кроме того, это стало неплохой идеей в качестве «боевого крещения» на Python — начал учить совсем недавно, и стало интересно, насколько быстро удастся (если удастся вообще) сделать такую штучку.

    Постановка задачи

    image
    Итак, нужно написать программу, которая будет реагировать на нажатия одних клавиш, и генерировать другие. В моем конкретном случае нужно генерировать нажатия всего четырех кнопок — Home, End, PgUp, PgDown. После перебора кучи возможных комбинаций, отброса существующих и важных для меня, я решил остановиться на комбинациях Alt + стрелки, а именно:
    • Alt + Left == Home
    • Alt + Right == End
    • Alt + Up == Page Up
    • Alt + Down == Page Down

    Что используем?

    Python 2.7 — эта версия достаточно хорошо поддерживается, в чем мне удалось убедиться;
    Python for Windows Extentions (build 216) — для использования WinAPI;
    pyHook 1.5.1 — удобная обертка для использования системных хуков (system hooks);
    py2exe 0.6.9 (опционально) — для создания standalone-исполняемого файла.

    Идея решения


    Общая идея достаточно проста: вешаем системный хук, отслеживаем нажатия. Если нажата Alt + стрелка — генерируем соответствующее системное сообщение о нажатии кнопки.

    Пряники и грабли
    1. Есть несколько моментов, которые требуют особого внимания:
    2. Клавиша Alt выделяется из других тем, что если она нажата, любое другое системное сообщение о нажатии кнопки будет «системным». Это значит, что непосредственно из сообщения о нажатии стрелки мы можем выяснить, нажата кнопка Alt в данный момент, или нет.
    3. Если мы попытаемся нажать кнопку PgUp при нажатом альте — ничего не произойдет. Точно так же и с генерацией системных сообщений. Выход прост: программно «отпустить» Alt перед генерацией, а потом «нажать» после.
    4. При нажатии (keystroke) каждой кнопки генерируется два сообщения — нажатие (key down) и освобождении (key up). Стоит их различать, иначе на один keystroke будет приходиться две симуляции нажатия нужной нам кнопки.
    5. Функция обработки сообщений возвращает True или False, и в зависимости от этого сообщение либо передается дальше по цепочке других системных хуков, либо не передается. Таким образом можно контролировать поток системных сообщений.

    Код программы

    # -*- encoding:cp1251 -*-
    import pythoncom, pyHook, win32api, win32con
    
    # функция обработки сообщений
    def OnKeyboardEvent(event):
        # если словили сообщение о нажатии левого альта...
        if event.Key == 'Lmenu':
            # ... а именно о том, что клавиша была отпущена...
            if event.MessageName[-2:] == 'up':
                # ... то мы ее таки отпускаем...
                win32api.keybd_event(win32con.VK_MENU, 0, 
                win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0)
            # ... и в любом случае мы дальше не передаем это сообщение в цепочку обработчиков
            return False
        # если нажата не стрелка - значит, передаем сообщение дальше по цепочке
        if not(37 <= event.KeyID <= 40 and event.MessageName == 'key sys down'):
            return True
        
        # иначе - обрабатываем его
        # поскольку альт нажат, то прежде, чем генерировать сообщения, нужно альт "отпустить"
        win32api.keybd_event(win32con.VK_MENU, 0, 
        win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0)
        # после этого посылаем сообщение
        if   event.KeyID == 38: win32api.keybd_event(win32con.VK_PRIOR, 0, 0, 0)
        elif event.KeyID == 40: win32api.keybd_event(win32con.VK_NEXT,  0, 0, 0)
        elif event.KeyID == 37: win32api.keybd_event(win32con.VK_HOME,  0, 0, 0)
        elif event.KeyID == 39: win32api.keybd_event(win32con.VK_END,   0, 0, 0)
        # и снова таки "нажимаем" отпущенный альт
        win32api.keybd_event(win32con.VK_MENU, 0, win32con.KEYEVENTF_EXTENDEDKEY, 0)
        # в любом случае, это сообщение дальше по цепочке не передается
        return False
    
    # создание экземпляра класса HookManager
    hm = pyHook.HookManager()
    # требуется отслеживать только нажатия клавиш
    hm.KeyAll = OnKeyboardEvent
    # вешаем хук
    hm.HookKeyboard()
    # ловим сообщения
    pythoncom.PumpMessages()
    
    


    Результат


    Такая программа действительно упростила жизнь. Правда, работает она далеко не «идеально».
    Во-первых, кнопка Alt потеряла полноту своей функциональности — теперь не получится просто так обратиться к меню приложения, нажав ее. Тем не менее, тот же Alt + F4 работает нормально. Впрочем, все основные системные хоткеи уже и так заняты, так что чем-то жертвовать в любом случае пришлось.
    Во-вторых, обнаружен следующий баг: сочетание Alt + Tab работает, но при освобождении кнопки Alt окошко переключения не пропадает. Пока что я не знаю, как это исправить, надеюсь, кто-нибудь подскажет.

    Выводы


    Глобальный вывод — «Python — это круто» (напомню, это первое, что я написал на питоне). Действительно, необходимые библиотеки находятся без труда, устанавливаются в два клика. Приятно, что так легко можно использовать то, что уже писалось другими.

    Вывод помельче касается непосредственно pyHook — пакет достаточно удобный. Уже появилось несколько идей, как можно будет его применить:
    • основываясь на перемещении курсора попытаться определить (приняв, например, 1 пиксель ~ 1 мм), какое расстояние проходит мышка (за час, за день, за все время наблюдений), ее средняя скорость, т.п.;
    • написать обычный клавиатурный keyhook в выводом в какой-нибудь лог-файл (давно хотел почувствовать себя шпионом :) );
    • составить частотный словарь своей речи. По сути тот же keyhook, только объединяющий отдельные буквы в слова, и подсчитывающий их частоту. Идея стырена навеяна давней перепиской с Skiminok — он делал подобный плагин для QIP.


    UPD: lxyd подсказал, что для всего вышеописанного существует программа AutoHotkey. Статья получилась в стиле «как я изобрел свой никому не нужный велосипед».
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 31
    • +3
      Если всё равно это только под Windows, почему не использовать AutoHotkey?
      Был топик на тему: habrahabr.ru/blogs/soft/130379/ (там чуть другая доработка раскладки, но переделать под себя — не проблема)
      А вот комментарий с более компактной записью: habrahabr.ru/blogs/soft/130379/#comment_4338231
      • 0
        Действительно, выглядит очень здорово, спасибо за подсказку.
      • 0
        Про мышь — у меня был спидометр для мыши ещё в 98 винде, так что идея не нова. Название, правда, уже не вспомню, но умела она считать расстояние, среднюю/текущую скорость, количество кликов и ещё что-то в том же духе.
        • 0
          Да, про пост-то я и забыл написать!
          По сути, этот пост — демонстрация работы с WinApi и хуками на питоне, а сама постановка задачи не столь интересна.
          • 0
            А в жизни, как и в науке, часто так случается — когда актуальная задача является рутинной, а вот что-то красивое и интересное может не иметь хорошего практического применения :)
        • +3
          а Fn+стрелки не работает? У меня на mini 2140 такая клавиатура
          www.3dnews.ru/_imgdata/img/2009/06/04/126539.jpg
          думаю должно работать
          • +1
            Нет, увы. Я тоже сначала был уверен, что именно так оно и должно работать, но, к сожалению, ошибался.
          • 0
            Когда-то пользовался вот таким: SetWindowsHookEx + CallNextHookEx
            • 0
              Если я не ошибаюсь, pyHook — это просто красивая обертка для этих функций.
            • 0
              И вместо alt можно опять же заюзать или Fn или Super ([Win]). Тогда не будет проблемы с меню приложения.
              • +1
                Fn, если я не ошибаюсь, не генерирует событий сама по себе, только совместно с «нужными» клавишами.
                • 0
                  Точно, ошибка вышла, извините. Хотя все равно у меня на Asus'е на клавишах навигации есть такие действия для управления плеером.
                • 0
                  Fn действительно сама по себе не генерирует сообщений, а Win генерирует только «key down» (как ни странно).
                  Возможный выход — попытаться в момент нажатия стрелки проверить, нажата ли Win через GetKeyboardState(). Нужно посмотреть, доступна ли эта функция в pywin32.
                • –4
                  Слово «windows» в начале упоминать надо. Если бы была более приличная ОС, я бы показал более приличные методы реализовывать этот же функционал.
                  • +1
                    Насчет «приличности» я не слишком разделяю Ваши взгляды. Но, тем не менее, никогда не поздно написать свою статью — благо, Хабр позволяет :)
                    • 0
                      Amarao! Для более приличной ОС я бы не отказался услышать рецепт. Дело в том, что для линукса я просто сваял свою раскладку с Level3Shift'ом на CapsLock'е и стрелками на hjkl. Но при таком решении отпадает возможность использовать капс для переключения раскладки, а это удобно и привычно. Если вы с этим справились, здорово было бы узнать как.
                      • 0
                        Именно с этой задачей я не сталкивался, благо, thinkpad'овские клавиатуры имеют на борту все нужные кнопки. В своё время вешал нужные команды на кнопки с помощью acpi.
                        • 0
                          Нужные кнопки на месте. Просто удобнее не бегать правой рукой между курсором и буквами, а оставаться на месте.
                          ACPI… Не знаю. А оно до xkb работает, или после? Просто после xkb кнопка — либо модификатор, либо кнопка и никак не обе вместе. Оттуда и проблемы.
                          • 0
                            Хм… Что-то я ступил: должно быть до. Ведь acpi и без иксов работает…
                            Спаибо за наводку, попробую поковырять!
                          • +1
                            Для xserver давно уже существуют xbindkeys и xmodmap, с помощью них можно переназначить любые клавиши (из тех, которые сама система может видеть) на что угодно да еще и запуск нужных программ сделать. Статей полно.
                            • 0
                              Да, даже движок «zoom» кошерной клавиатуры одной очень некошерной фирмы можно прикрутить.
                              Вообще, гибкость — это хорошо.
                              • 0
                                Благодарю! Обязательно посмотрю их поподробнее. Это как минимум должно быть удобней, чем раскладку для xkb писать.
                                Хотя я пока всё же сомневаюсь, что оно сможет сделать то, что хотелось бы поиметь в идеале:
                                CapsLock + j = «вниз» (по нажатию j)
                                один CapsLock = переключение раскладок (по отпусканию)
                                • 0
                                  Не уверен, что такое возможно сделать с помощью Xmodmap. Советую заглянуть на LOR Wiki ( www.linux.org.ru/wiki ), там может быть совет, как сделать подобное. Либо спросить на форуме того-же сайта.
                          • 0
                            >(приняв, например, 1 пиксель ~ 1 мм)
                            Не получится так. Перемещение курсора на экране зависит от скорости перемещения мышки. Абсолютное изменение координат устройства в Windows, скорее всего, можно получить, только напрямую работая с драйвером. Или даже только написав свой драйвер. Ну, мне так кажется.
                            • 0
                              Проще будет достать значение скорости мыши с настроек и сопоставить.
                              Хотя и не будет так точно, но более реально.
                            • +1
                              Можно сделать раскладку, в которой с нажатым правым Alt (AltGr) некоторые клавиши становятся клавишами управления курсором. Тогда даже не нужно снимать правую руки со стартовой позиции печати ([a][s][d][f][ ][ ][j][k][l][;]), чтобы передвинуть курсор.

                              Собственно я так и работаю уже давно в Linux, не знаю правда, насколько это будет работать в Windows, скорее всего проблем не будет. Использую «Miniguru» схему: [i][j][k][l] — стрелки, [u][o] — Home/End, [p][;] — PgUp/PgDn, [m] — Delete, [,] — Insert. При желании можно сделать как в VIM — [h][j][k][l].


                              • +1
                                Ibnteo, тогда к вам тот же вопрос, что я выше Amarao задавал: как это реализовано? Своя раскладка для xkb или ещё что-то?
                                • +1
                                  На картинке есть ссылка на статью.

                                  Я правил файлы нужных мне раскладок xkb в /usr/share/X11/xkb/symbols/
                                  Видимо правильно будет создать отдельный файл и подключать его как-то, я не знаю как это реализовать.
                                  • +1
                                    Спасибо. Коммент писал ещё не проснувшись, извините, ссылку не заметил :)
                                    Тогда этот как раз тот, который применил я, жаль.
                                    Напишу уж тогда кстати, как свои файлы подключал, может пригодится:
                                    файл /usr/share/X11/symbols/capsext
                                    А это надо добавить в .xinitrc:
                                    setxkbmap -compat «complete+ledscroll(group_lock)» -symbols «pc+capsext(us_capsext)+capsext(ru_capsext):2+inet(evdev)»
                              • 0
                                А что дальше происходит с этим скриптом? Его надо во что-то компилировать или попросту запускаете .py при старте системы?
                                • 0
                                  При помощи py2exe билдится приложение, которому для запуска питон не нужен. Делается это достаточно просто — на сайте (ссылка в статье) есть подробное описание.
                                  Приложение собирал так, чтобы консоль сразу пряталась при запуске, и все это — в Run раздел реестра. При запуске системы оно запускается и тут же прячется — отключить (в крайнем случае) можно через процессы.

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