Главная » Delphi » Самый простой переключатель раскладки

0

Перенесем наш проект из папки Glava6\2 под новым именем Keyswitch в другую папку (Glava7\l) и туда же перенесем проект DLL, также под новым именем KeyHook. Не забудьте, что при переносе надо отредактировать DSK- файлы для двух проектов (и для ловушки, и для основной программы).

Заменим главную иконку приложения в ресурсном файле Keyswitch.res на другую подходящую (в папке с проектом это letters.ico) все тем же способом (см. главу 5). Для того чтобы перейти теперь к созданию непосредственно "переключалки", нам сначала понадобится заголовок окна программы, который у нас до сих пор оставался пустым — см. процедуру createWindowEx. Вставим в нее, наконец, настоящее имя окна "KeySwitch" (третий параметр). Так как мы создаем не учебный пример и не рабочую утилитку, а настоящее приложение всерьез и надолго, то озаботимся тем, чтобы оно не запускалось второй раз. Для этого внесем наш рабочий дескриптор dwin в список переменных (можно через запятую перед instance), добавим туда же переменные st:string; и pbuff:array[0..127] of char; И сразу после CreateWindowEx вставим нашу процедуру предотвращения повторного запуска:

FHandle:=CreateWindowEx (0,’DX’,’KeySwitch’,WS_POPUP, 5,5, 200,

200,0,0,instance, nil); {получаем дескриптор окна!

dWin:=FHandle;

while dWin <> 0 do

begin

if (dWin <> FHandle) and {собственное окно игнорируем! (GetWindow(dWin, GWJOWNER) = 0) and

{дочерние окна игнорируемI (GetWindowText(dWin, pbuff, sizeof(pbuff)) <> 0) (без названия игнорируем}

then begin

GetWindowText(dWin, pbuff, sizeof(pbuff));

{получаем текст названия приложения) st:=etring(pbuff); {переводим его в строку) if st=’KeySwitch* then exit; {прерываем программу) end;

dWin: = GetWindow (dWin, GWJWNDNEXT);

{ищем следующее приложение из списка)

end;

Теперь вернемся к перехватчику — нам нужно отработать процедуру передачи события перехваченной клавиши вызывающей программе, ради чего все и затевалось. Для этого надо как-то определять окно, куда идет передача. Самый грамотный путь — воспользоваться уже упоминавшимся в главе 4 механизмом отображения файлов на память (memory mapped files)— в этом случае программы (ловушка и собственно переключатель) получают общий кусок памяти, через который все и передается. А нельзя ли попроще? Ведь пока нам никаких особенных параметров передавать не надо, достаточно только зафиксировать сам факт события. Мы знаем уже, что есть такая функция SendMessage, и она должна что-то передавать окну по известному дескриптору. У нас в главе 6 это более-менее работало, но в таком случае поиск приемного окна по дескриптору и даже прямая передача дескриптора ловушке при запуске не сработают— событие передастся только, если окно вызывающей программы активно (или ловушка локальная). А у нас оно не только не активно, но еще и спрятано в иконку. Так что вместо того, чтобы пытаться передать дескриптор, мы поступим примерно так, как в процедуре предотвращения повторного запуска— найдем дескриптор окна программы по его имени. Правда, если мы захотим позднее сделать выбор горячей клавиши при запуске программы, то механизм передачи параметров через memory mapped files так или иначе использовать придется. Но пока не будем на этом сосредотачиваться, а попробуем применить самое простое и к тому же уже отработанное решение— как мы увидим в дальнейшем, оно вполне работоспособно.

А какое сообщение передавать? Вставим и в текст ловушки, и в текст самого перехватчика в секции объявлений переменных такую константу: HookMsg

=wm_User +$Ш ;.

Тут мы число выбрали "от фонаря" — в надежде, что никому другому такое в голову и не придет. Для единообразия я заменил и icoMessage (хотя это необязательно), а потом поместил их обе в секцию констант. Теперь будет всего три константы:

const

idmEXIT=l; {пункт всплывающего меню "Закрыть"/ Ico_Message = wm_’Jser+5110; (сообщение от «колют окну) HookMsg =wm_User+$l11; (сообщение от ловушки)

Далее, когда мы будем писать программу для предоставления ее во всеобщее пользование, я покажу, как зарегистрировать уникальное сообщение через функцию RegisterWindowMessage (получив перед этим для него уникальный номер через <CtrI>+<Shift>+<G>).

Теперь перейдем, наконец, к ловушке. Пусть она пока будет реагировать, к примеру, на правый (цифровой) <Enter>. Вычистим из DL.L все упоминания о записи в файл — она нам больше не понадобится (а в проекте Keyswitch это место можно пока временно закомментировать — запись в файл нам потом понадобится для другой цели). Исходный текст DLL должен в результате быть таким — листинг 7.1.

j Листинг 7.1. Модифицированная библиотека для "шпионской" ловушки

library Keyhook;

uses

Messages, Windows;

var

HookHandle:hHook;

function KeyboardProc(Code: Integer; wParam: wParam; LParam: LParam): integer; stdcall;

begin

if code<0 then Result:CallNextHookEx(HookHandle, code, WParam, LParam)

else begin

if byte(LParam ahr 24)<$80 then !только нажатиеI begin

Result:=CallNextHookEx(HookHandle, code, WParam, LParam); if byte (LParam ahr 16)= $1C than {клавиша Enter) if byte(LParam shr 24)=1 then {конкретно – дополнительная) begin

SendMessage(FindWindow(‘DX’,’KeySwitch’), HookMsg, WParam, LParam) ;

end; end; end; end;

prooedure SetHook; etdcall; {установка ловушки) begin

HookHandle := SetWindowsHookEx(WH_KEYBOARD,KeyboardProc, hlnstance, 0);

end;

procedure DelHook; etdcall; {удаление ловушки} begin

UnhookWindowsHookEx(HookHandle); end;

exports

SetHook, DelHook;

begin

end.

He забудем заменить и ссылки на DLL в объявлении процедур SetHook и DelHook В начале программы на Keyhook.dll И В процедуре CreateMyicon всплывающую подсказку на KeySwitch.

Теперь обратимся к программе и подумаем — а что мы, собственно, будем делать по событию HookMsg? Однозначно— имитировать нажатие клавиш переключения раскладки, но ведь здесь может быть два варианта: <Ctrl>+<Shift> или <Alt>+<Shift>. Какой именно установлен в системе, можно узнать из реестра, но надо ли? Давайте просто сымитируем оба варианта — при этом мы ничего не нарушим, т. к. какой-нибудь сработает, а второй просто пропадет впустую. Итак, пишем такую процедуру (текст нужно расположить до обработчика сообщений windowProc):

procedure LayoutChange; begin

keybd_event(VK_SHIFT,$2a,0,0); (<Ctcl>+<Shift>) keybd_event(VK_CONTROL,Sid, 0,0) ; keybd_event(VKJ^ONTROL,$ Id,KEYEVENTF_KEYUP,0); keybd_event(VK_SHIFT,$2a,KEYEVENTF_KEYUP, 0) ;

keybd_event{VK_MENU,$38,0,0); {<Alt>+<Shifc>} keybd_event(VK_SHIFT,$2a,0,0); keybd_everit (VK_SHIFT, $2a,KEYEVENTF_KEYUP, 0) ; keybd_event(VK_MENU,$38,KEYEVENTF_KEYUP, 0); end;

Теперь вставляем обработчик нашего сообщения в конец процедуры

WindowProc:

if Msg = HookMsg then LayoutChange;

Обратите внимание на один момент— ранее мы утверждали (не как-нибудь, а руководствуясь официальной информацией с сайта MSDN), что второй параметр функции keybd_event, который должен представлять собой скан-код, все равно игнорируется, и потому может быть проставлена любая величина. И все работало именно так! А вот в данном конкретном случае, если вы немного поэкспериментируете, то обнаружите, что под Windows ХР все по- прежнему работает в любом виде, а вот под Windows 98 — нет! Приходится вводить настоящий скан-код. Вот они, недокументированные особенности Windows во всей красе!

Прежде чем пробовать эту программу, удалите из системы другой подобный переключатель раскладки, если он у вас установлен — почти 100% гарантии, что он устроен аналогичным образом, и тогда конфликты неизбежны. А теперь запустите программу (предварительно, естественно, откомпилировав ловушку Keyhook) и проверьте — по правой клавише <Enter> раскладка переключается, по обычной — нет. Но! Клавиша <Enter>, кроме всего прочего, потому и называется так (ввод), что еще и делает много других действий: переводит строку в текстовых редакторах, запускает программы и т. п. И ведь точно так же будет со всеми остальными клавишами, это только <Ctrl> и <Shift> ничего сами по себе ие делают, но нельзя же ограничиться таким скудным выбором! К счастью, выход из этой ситуации имеется — в случае, если нажата "наша" клавиша, надо просто "не пускать" это событие дальше нашей ловушки. Это очень просто: нужно, чтобы функция KeyboardProc возвращала значение, отличное от 0. Перепишем этот фрагмент в тексте ловушки Keyhook так: function Key board!? г ос (Code: Integer; wParam: wParam; IParam: IParam):

integer; stdcall;

begin

if code<0 then Result:=CallNextHookEx(HookHandle, code, WParam, LParam)

else

begin

if byte(LParam shr 24)<$80 then (только нажатие) begin

Result:=CallNextHookEx(HookHandle, code, WParam, LParam); if byte (LParam shr 16)= $1C then (клавиша Enter) if byte(LParam shr 24)=1 then (конкретно – дополнительная) begin

SendMessage(FindWindow(‘DX’,’KeySwitch’), HookMsg, WParam, LParam);

Result:=1; end; end; end; end;

Теперь обычная клавиша <Enter> будет работать по-прежнему, а цифровая только переключать раскладку. Проверьте и убедитесь, что со всеми клавишами дело обстоит точно так же, за одним исключением: те клавиши, обработка которых ведется на уровне BIOS, все равно отключить не удастся, т. е. <Caps Lock>, к примеру, по-прежнему будет переключать регистр. Этим фактом мы можем воспользоваться по-своему и дополнительно ко всему осуществить одну красивую операцию — отключить <Alt> как способ входа в меню. Для отключения этой ненавистной функции достаточно добавить к тексту KeyboardProc одну-едннственную строку: if byte (LParam shr 16)= $38 then Result:=l;

$38 — общий скан-код для обеих клавиш <Alt>. Произойдет следующее: системная функция <Alt>, как модификатора, будет работать, как ни в чем не бывало, а функция входа в меню исчезнет. Однако не все, конечно, разделяют мою ненависть к этой особенности Windows’, поэтому в данном варианте можете включить эту возможность в программу по желанию, а позднее, когда мы будем делать расширенный вариант переключателя, придется ее сделать опциональной — т. е. устанавливаемой по желанию пользователя. К сожалению, так же просто отключить функции системных клавиш Windows (тех, что для вызова меню Пуск и контекстного), чтобы их использовать в своей программе, не удается.

Попробуем и убедимся, что все работает (могут наблюдаться "глюки" в Office ХР, но далее мы укажем, как от этого недостатка избавиться). Простейший переключатель готов! Заметим, что если вы захотите на этой основе сделать, например, просто программу выключения функции <AIt>, а раскладку переключать другой программой, то они могут конфликтовать. Конфликта можно избежать очень просто: надо сделать так, чтобы наша программа запускалась раньше фирменной "переключалки", тогда все сообщения дойдут куда надо: чем раньше запущена ловушка, тем позже до нее доходит сообщение в очереди. Осуществить это несложно — надо включить нашу программу в автозапуск через реестр (как это делается, мы увидим в дальнейшем), а чужую — через папку Автозапуск, тогда порядок будет наверняка именно такой, какой надо. Если же включать в автозапуск программу целиком, то другую "переключал ку" придется удалить (да и зачем она нужна?), но в некоторых случаях могут быть иные конфликты.

Уж больно переключатель получился примитивный: во-первых, не позволяет пользователю выбирать по своему усмотрению горячую клавишу для переключения. Во-вторых, иконка, с помощью которой Windows сообщает нам о действующей раскладке, довольно невыразительная — в нее надо долго вглядываться, чтобы понять, какой язык установлен, и неплохо бы ее заменить, например, на красную и синюю, которые будут меняться в зависимости от раскладки. А это повлечет за собой целый ворох проблем: для начала нужно обеспечить двустороннюю связь между системным переключателем и нашим (вовсе отключить системный мы не можем, т. к. работаем через него). Иначе, если кто-то переключит раскладку по привычке двойными клавишами или мышью, у нас ничего не переключится. Еще сложнее обеспечить автоматическое распознавание текущей раскладки активного окна с тем. чтобы она переключалась синхронно с системной при переходе от одного приложения к другому.

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

Не откажем себе в удовольствии отвлечься, чтобы очередной раз попинать Microsoft: вполне можно было бы и не делать свою рвекладку для каждого приложения, а оставить общую раскладку для всей системы или хотя бы предоставить пользователю выбор. Одна текущая раскладка на все была бы даже лучше — переключаясь между приложениями, сейчас нужно все время поглядывать на индиквтор, что очень отвлекает. В противном случве это откладывалось бы в голове и никакой индикатор вообще бы не понадобился (еще в Windows 3.x автор этих строк никогда индикатором не пользовался). А если уж делать, то довести идею до ума, и предоставить возможность запоминания раскладки по умолчанию для каждого приложения отдельно.

Займемся сначала второй задачей. Ясно, что придется переписать DLL, и настолько существенно, что мы перенесем наш проект из папки Glava7\l в новую папку (Glava7\2), дабы не потерять достигнутое. Так как простейшая программа тоже вполне работоспособна, то проект я переименовал в LangSwitch, а проект DLL назвал Langhook.dll. Заголовок окна и всплывающую подсказку мы также заменим, но во избежание конфликтов следует помнить, что одновременно запускать эти "переключалки" нельзя.

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

По теме:

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