Главная » Delphi » Клавиатурный шпион

0

Для того чтобы отработать создание ловушек и заодно создать заготовку для переключателя клавиатуры, мы сначала сделаем простой вариант ловушки, который будет ничем иным, как типичным Spyware — т. е. программой- шпионом, которая перехватывает нажатия всех системных клавиш и отправляет их в текстовый файл. Настоящий "шпион" должен еще регистрироваться в реестре для автоматического запуска и периодически отправлять созданный файл по электронной почте, а нам это ни к чему — мы будем создавать такой перехватчик, разумеется, не для шпионажа, и даже не просто для тренировки, а с вполне прагматической целью— изучение описанного ранее параметра LParam для всяких дополнительных клавиш, которые нельзя различить через MapVirtualKey.

Для того чтобы создать такой перехватчик, мы используем заготовку программы Layout из предыдущей главы (из папки Glava5\3). Перенесем проект в новую папку (Glava6\2 на диске) под другим именем (через пункт File | Save

Project As) — пусть он теперь называется KeySpy. Раз такое название (spy — шпион), то и иконка должна быть соответствующая (на диске она называется Eye.ico). Заменим ею MAINICON в файле Keyspy.res через Image Editor способом, уже описанным в главе 5 в разд. "Заготовка". Затем внесем в проект следующие изменения: уберем из проекта все, что относится к горячей клавише (В том числе обработчик сообщения WM HOTKEY, функции RegisterHotKey и UnregisterHotKey), а также сразу заменим неудобное сообщение, которое появляется при нажатии правой кнопки мыши на всплывающем меню — оно все равно нам понадобится в дальнейшем. Пока меню это будет состоять из одного пункта Закрыть. И не забудем заменить в процедуре CreateMyicon всплывающую подсказку на Key spy.

Для создания всплывающего меню сделаем следующее. Сначала в секции объявления переменных вставим строки:

PopMenu: hMenu; (всплывающее меню) Pos:TPoint; (позиция курсора}

А выше, перед словом var, добавим константу, которая будет идентифицировать пункт (пока единственный) нашего меню: const

idmEXIT =1;

Теперь в конце программы (после оператора CreateMyicon) создадим всплывающее меню и добавим в него этот самый пункт:

popMenu := CreatePopupMenu;

AppendMenu(PopMenu,MF_STRING,idmEXIT,’Закрыть’) ;

Осталось, во-первых, вовремя вызвать меню на экрвн, во-вторых, создать обработчик сообщения (wm_C0MMAND), которое оно посылает. Вызываем его мы, естественно, нажатием правой кнопки мыши, как и раньше MessageBox:

if Msg = IcoMassage then (если это сообщение от иконки} begin

if lpr=wm_RBUTTONUP then (была отпущена правая кнопка} begin

GetCursorPos(Pos); {узнаем позицию курсора мыши} SetForegroundWindow(FHandle);

(установим окно вперед – так рекомендует Microsoft) TrackPopupMenu(PopMenu,ТPM_RIGHTALIGN+T PM_RIGHTBUTTON, Pos.x,Pos.y,0,FHandle,nil); (показываем меню)

PostMessage(FHandle, WM_NULL, 0, 0); {это тоже рекомендует Microsoft) end; end;

Теперь обработчик (ниже, внутри той же процедуры windowproc):

if Msg = wm_COMMAND than if wpr =idmEXIT then begin

Shell_NotifyIcon(NIM_Delete,@noXconData); {удаляем иконку)

halt; {закрываем программу)

end;

Если кому интересно — подробности об использовании этих функций и сообщения wm_coMMAND см. в [14, 16]. Перед созданием ловушки в программе надо еще подготовить файл, куда будем сбрасывать перехваченные коды. Для этого в начале добавим переменную ft: textfile, а в конце программы (после создания меню, но перед циклом сообщений) будем его создавать:

assignfile(ft,’c:\Keyhook.txt’); {создаем файл в корневой директорииI

try

reset(ft); {пробуем открыть) except

rewrite(ft); {если не получается, то создаем) end;

closefile(ft); {закрываем файл)

Обратите внимание, что при указании имени файла нужно указывать весь путь, т. е. размещать его в конкретной папке — иначе наша ловушка его может не найти. Мы поместили файл туда, где его проще всего потом разыскать, но настоящие "шпионы", разумеется, прячут его куда подальше — в недра системных папок.

Приложение мы подготовили, теперь перейдем к созданию собственно ловушки. Ловушки бывают локальные и глобальные. Локальная ловушка — это фактически параллельный дополнительный поток в нашем приложении, и перехватывать нажатия клавиш извне она не умеет, так что придется сооружать глобальную ловушку. Гповальные ловушки помещаются в DLL — придется создавать отдельную DLL-библиотеку и, главное, потом всюду ее "таскать" вместе с приложением. Поморщимся, но что поделаешь?

Не удивляйтесь, но, несмотря на громкое название, "шпионская" ловушка — одна из самых простых, потому что она только перехватывает клавиши и записывает их в файл (когда мы перейдем к переключателю, все будет несколько сложнее). Для создания DLL выполним команду File | New | Other | DLL Wizard и в получившуюся заготовку впишем текст из листинга 6.1 (заменив им все, что нам предлагает Delphi).

procedure DelHcok; stdcall; {удаление ловушкиI begin

UnhookWindowsHookEx{HookHandle); end;

exports

SotHook, Delhook; begin

assignf i le (f r., ‘a: \Keyhook. txt:’} ; end.

Заметим, что запись в дисковый файл прямо из ловушки — дело довольно рискованное с точки зрения устойчивости программы и системы в целом. Прежде всего, потому что жесткий диск — устройство медленное, и вся процедура будет тормозить, к тому же мало ли что может случиться с дисковым файлом. Поэтому правильно организованный "шпион" должен передавать нажатие в основную программу, а она уже— писать в файл, что намного безопаснее. На том, как именно передаются параметры, мы остановимся позже. А здесь не будем себе морочить этим голову, т. к. в данном случае программа делается исключительно в утилитарных целях.

Заметки на полях

По правилам, изложенным в справке от Microsoft, программа при обработке системной ловушки должна проверять параметр ccde на условие <0 и в этом случае немедленно вызвать функцию CailNextHookEx с выходом из процедуры, а обрабатывать сообщение (от клавиатуры) только, еслл code – HC_ACTION. Какими только вариантами реализации этого условия не полнятся исходные тексты на соответствующих форумах! От полного игнорирования до сложных конструкций if… then… else… else… else. На самом деле Win32 никогда не возвращает значения code меньше 0 (это было только в Windows Зх), и именно поэтому любые варианты работают практически одинаково, но мы все же будем поступать по правилам. А параметр нс_астю : (только для сообщений от клавиатуры) показывает, должна ли функция обработать сообщение. На практике он не оказывает никакого влияния на Обработку события — по крайней мере, мне такого влияния установить не удалось.

Другое дело, что установленные ловушки никак не сортируются — это официальная информация! — потому никто не гарантирует, что сообщение до вашей ловушки дойдет. И все же на практике этот механизм работает на удивление прилично. Хотя, например, автором при работе в среде Windows 98 были замечены редкие проскоки событий нвжатия клавиш мимо ловушки. В другой (но. несомненно, сделанной по тому же механизму) программе для переключения раскладки, скачанной из Сети, наблюдался тот же эффект. Надежность перехвата клавиши зависит от установленного в системе софта, которого у автора в Windows 98 не просто много, а очень много, и на некоторых связанных с этим особенностях использования ловушек мы еще остановимся.

Пояснять подробно использование функций SetWindowsHookEy. И Keyboard?roc я не буду — это занятие долгое, и вы прекрасно можете найти все подробности в Интернете. Выражение byte(L?aram shr 2^)<$80 означает следующее: внутри скобок длинное слово LParam сдвигается на три байта вправо, т. е. младшим оказывается старший байт, затем полученное число усекается до байта непосредственным преобразованием типов (последнее, кстати, в данном случае необязательно и сделано только для наглядности). Как мы знаем из вышеизложенного, если старший бит в полученном байте равен I (т. е. байт больше 127 = $7F), то это код отпускания, а нам нужен код нажатия, отсюда и условие. Если бы мы его не включили, то каждая клавиша отслеживалась бы дважды: на нажатие и на отпускание. Заметим, что есть, разумеется, и иные способы ограничить действие т олько одним из сообщений.

Перехваченные коды клааиш (виртуальный код в wParam и все остальные подробности, включая скан-код и признак расширенной клавиши — в ьрагаш) сохраняются в файле в Нех-форме благодаря использованию модуля Ariplini, как и ранее. Между begin и end выполняется код инициализации — в данном случае ассоциация с именем файла. Имейте в виду, что DLL нужно компилировать отдельно от использующей ее программы. Формально это совершенно разные программы, что, конечно, отвечает настоящему положению вещей, поэтому сначала создайте проект библиотеки отдельно, а при отладке удобно запускать два экземпляра Delphi — каждый со своим проектом (только не запутайтесь в плавающих окнах!), т. к. открыть два проекта в одном окне Delphi не позволит. Проект с DLL назовем Spyhook, и получим в результате компиляции файл Spyhook.dll.

Теперь нам осталось запускать ловушку вместе с нашим приложением и затем ее как-то удалять. Для этого используем экспортируемые из DLL процедуры SetHook и DelHook. Сначала допишем в самом начале (после предложения uses) ссылки на эти функции (предполагаем, что DLL находится либо в той же папке, что и программа, либо в системной папке, скажем, в Windows или Windows\System):

procedure SetHook; stdcall; External 1Spyhook.dil'; procedure DelHook; stdcall; External ‘Spyhook.dll';

Заметки на полях

Обратите внимание, что обычно а настоящее время при написании DLL индексы экспортируемых функций не используются, как рекомендует, например, [3] — начиная, по крайней мере, с Windows 95 эти функции ищутся по имени, а не по индексу. Хотя строковый поиск несколько дольше, чем численный — какое это имеет значение в современных компьютерах с гигагерцовыми процессорами? Практиковавшееся ранее использоаание индексов— когда каждую функцию вызывали по ее номеру, присвоенному программистом при написании DLL— гораздо менее удобно. Единственное затруднение, которое может при этом возникнуть при вызове функций из чужих библиотек, — то, что в С и С++ разрешены имена с использоаанием знака a Delphi их "не понимает". Это ограничение можно обойти, организовав вызов функции из библиотеки, например, CppMade.dll с именем cppCorrectName@o по следующему образцу:

function DelphiCcrrectName:integer external ‘CppMade.dll’ name ‘CppCorrectNaitie80′ ;

Такой способ переименования через name можно использовать и для придания видимых извне имен в самой DLL. Правда, внешние имена при этом будут чувствительны к регистру букв — а нужны ли нам такие сложности? Но следует иметь в виду, что другие могут так не посчитать и потому при аызова функций из чужих DLL лучше соблюдать тот регистр, который указан в ааторском описании библиотеки.

Теперь, наконец, включим вызовы этих функций в нужные места: устанавливаем ловушку мы при запуске программы, поэтому включаем вызов в конце, после создания файла:

closefile(ft); {закрываем файл) SetHook; {запускаем шпиона)

А удаление включим перед вызовом halt (он у нас повторяется два раза):

Shell_NotifyIcon(NIM_Delete,GnoIconData); {удаляем иконку) DelHook; {удаляем шпиона) halt; {закрываем программу)

Заметки на полях

При отладке в Delphi программ, использующих ловушки или другие функции, находящиеся а отдельно запускаемой DLL, обязательно закрывайте запущенную в отладочном режиме программу перед аыходом из Delphi или перед загрузкой другого проекта — особенно при работе в Windows ХР. Попытка автоматически прервать процесс может привести к полному зависанию всей системы.

Все готово — можете запускать программу и отслеживать клавиши. Только перед просмотром файла Keyhook.txt ловушку желательно каждый раз выгружать — если используемый просмотрщик при открытии файла монопольно им завладевает (как это делает MS Word), то любое случайное нажатие на клавиши, скорее всего, обрушит открывшую программу, несмотря на наши предосторожности. Это не относится к "правильно" написанным редакторам, которые каждый раз после открытия файла загружают его содержимое, затем закрывают его и оставляют в покое (при необходимости отслеживая сторонние изменения в нем при активации их окна). К таким редакторам, в частности, относится и встроенный редактор Delphi. Автор рекомендует для работы с текстовыми файлами (в частности, с HTML) вообще использовать не Блокнот, а какой-нибудь из подобных редакторов — например, Edit Plus.

Полученный "шпион" перехватывает абсолютно все клавиши, как вы можете убедиться сами, и возвращает при этом различный скан-код (третий байт + младший бит четвертого в параметре LParam). Именно с его помощью автор обнаружил у своей клавиатуры интересную особенность, о которой раньше и не подозревал — правый <Alt>, оказывается, у нее отсутствует вообще, а находящаяся на его месте клавиша имитирует два нажатия: код обычной (левой) клавиши <Alt> + код также обычной (левой) <Ctrl>. Не очень понимаю, зачем пришло такое в голову разработчикам этой замечательной клавиатуры (обычной конфигурации, но с "ноутбучными" клавишами)— ведь одновременное нажатие <Alt>+<Ctrl> требуется разве что, если вы в отчаянии посылаете систему на известные "три клавиши", а из знакомых автору продуктов употребляется только в Borland ModelMaker (далее мы, впрочем, тоже это сочетание используем). То, что это делает именно клавиатура сама, а не система— гарантированно, т. к. никаких драйверов я не ставил. Но данная особенность — хороший пример того, почему нельзя назначать переключателю раскладки какую-то определенную клавишу, необходимо предоставить пользователю выбор, иначе на каких-то компьютерах (особенно на ноутбуках) может оказаться, что ничего не работает.

Источник: Ревнч Ю. В.  Нестандартные приемы программирования на Delphi. — СПб.: БХВ-Петербург, 2005. — 560 е.: ил.

По теме:

  • Комментарии