» Расшифровка скан-кодов клавиш Borland Delphi. windows. . Блог программистов


Блог программистов






201131 Май

Расшифровка скан-кодов клавиш

Здравствуй, читатель! В этой небольшой статье я расскажу, как конвертировать скан-коды клавиш в виртуальные коды и как получать название клавиши по её скан-коду. Если вдаваться в суть дела, то я просто расскажу, как работают функции GetKeyNameText и функции MapVirtualKey. То, что я здесь опишу, возможно, вам никогда не пригодиться, разве что вы сами не захотите создать свою раскладку клавиатуры. Так же мы напишем программу на Delphi для расшифровки данных из файла клавиатурного лога, который создаёт драйвер-фильтр, написанный нами в предыдущей статье.

   Итак, мы знаем, что клавиатура выдаёт только скан-коды клавиш, интерпретация скан-кодов полностью зависит от текущей раскладки клавиатуры. Например, при нажатии на кнопку «[» при включённой английской раскладке мы получим символ «[», при включённой русской раскладке мы получим символ «х». Нам надо выяснить какой компонент в системе отвечает за это преобразование.
   Не буду долго размусоливать эту тему, сразу скажу: за преобразование скан-кодов отвечает специальная DLL, называемая DLL раскладки. Эти DLL находятся в папке Windows\System32 под названием kbd*.dll. Русская раскладка находится в файле kbdru.dll, американская в kbdus.dll, татарская в kbdtat.dll и т.д.
   DLL раскладки содержит полную информацию о преобразовании скан-кодов, а именно:

  1. Скан-коды в виртуальные коды
  2. Виртуальные коды в скан-коды
  3. Скан-коды в названия клавиш
  4. Виртуальные коды во флаги модификаторы (SHIFT, CTRL и т.д.)
  5. Виртуальные коды в текстовые символы

   Эти DLL используются системой при вызове функций GetKeyNameText и MapVirtualKey. Но при этом никто нам не мешает их использовать, ведь обычные DLL которые можно закгрузить.
Каждая DLL kbd*.dll экспортирует только одну stdcall функцию — KbdLayerDescriptor. Функция не принимает параметров и возвращает указатель на структуру KBDTABLES.


typedef struct tagKbdLayer {
    PMODIFIERS pCharModifiers;
    PVK_TO_WCHAR_TABLE pVkToWcharTable;  
    PDEADKEY pDeadKey;
    PVSC_LPWSTR pKeyNames;
    PVSC_LPWSTR pKeyNamesExt;
    WCHAR *KBD_LONG_POINTER *KBD_LONG_POINTER pKeyNamesDead;

    USHORT  *KBD_LONG_POINTER pusVSCtoVK;
    BYTE    bMaxVSCtoVK;
    PVSC_VK pVSCtoVK_E0;  
    PVSC_VK pVSCtoVK_E1;  
    DWORD fLocaleFlags;
    BYTE       nLgMax;
    BYTE       cbLgEntry;
    PLIGATURE1 pLigature;
#if (NTDDI_VERSION >= NTDDI_WINXP)
    DWORD      dwType;     
    DWORD      dwSubType
#endif
} KBDTABLES, *KBD_LONG_POINTER PKBDTABLES;

   Задача конвертирования скан-кода и виртуальный код очень легко решается: поле pusVSCtoVK содержит указатель на массив элементов, каждый из которых хранит виртуальный код. Чтобы получить виртуальный код какого-либо скан-кода, достаточно обратиться к элементу с индексом равным скан-коду. На Delphi это делается следующим кодом:


function GetVirtualCodeFromScanCode(pKbdTable:PKBDTABLES; KeyScanCode:Word):Word;
begin
  Result:= PWord(DWORD(pKbdTable^.pusVSCtoVK)+KeyScanCode*sizeof(Word))^;
end;

   Задача получения имени клавиши по её скан-коду сложнее, но не на много. Для конвертирования скан-кода в название клавиши предназначены два поля структуры KBDTABLES: pKeyNames и pKeyNamesExt. Они указывают на массив структур VSC_LPWSTR.


typedef struct {
    BYTE   vsc;
    WCHAR *KBD_LONG_POINTER pwsz;
} VSC_LPWSTR, *KBD_LONG_POINTER PVSC_LPWSTR;

где vsc – скан-код клавиши, pwsz – указатель на UNICODE строку, содержащую название клавиши (например, Enter, Backspace и т.д.).
   Каждый массив заканчивается элементов с полем vsc равным нулю. Для получения имени клавиши достаточно пройтись по этим двум массивам. Делает это следующая функция:


function GetKeyNameW( pKbdTable: PKBDTABLES; KeyScanCode: word): PWChar;
var
  pCurrItem:PVSC_LPWSTR;
begin
  Result:=nil; 
  pCurrItem := pKbdTable^.pKeyNames;
  while pCurrItem^.vsc0 do
   begin
    if pCurrItem^.vsc = KeyScanCode then
     begin
      Result:= pCurrItem^.pwsz;
      exit;
     end;
    pCurrItem := PVSC_LPWSTR(DWORD(pCurrItem)+ sizeof(VSC_LPWSTR));
   end;
   
  pCurrItem := pKbdTable^.pKeyNamesExt;
  while pCurrItem^.vsc0 do
   begin
    if pCurrItem^.vsc = KeyScanCode then
     begin
      Result:= pCurrItem^.pwsz;
      exit;
     end;
    pCurrItem := PVSC_LPWSTR(DWORD(pCurrItem) +sizeof(VSC_LPWSTR));
   end;
end;

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


var
  KeyPressed: array[0..6] of WCHAR = ('D', 'O', 'W', 'N', ' ', ':', ' ');
  KeyReleased: array[0..4] of WCHAR = ('U', 'P', ' ', ':', ' ');
  NewLine: array[0..1] of WCHAR = (#13, #10);

procedure TMainForm.ScanCodesToKeyNames(Buffer: PWord; BufferSize:DWORD; FileName:PChar);
var
  KeyName : array[0..23] of WCHAR;
  CurrKeyName: PWChar;
  FileHandle:THandle;
  _Writed:DWORD;
  _i, _c:Integer;
  cVK:Word;
begin
  FileHandle:=CreateFile(FileName, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
  if FileHandle=INVALID_HANDLE_VALUE then Exit;
  _c:=BufferSize div sizeof(Word);
  for _i:=1 to _c do
   begin
    if ((Buffer^ shr 8 ) and KEY_BREAK)=KEY_BREAK then
     WriteFile(FileHandle, KeyReleased, SizeOf(KeyReleased), _Writed, 0)
                                               else
     WriteFile(FileHandle, KeyPressed, SizeOf(KeyPressed), _Writed, 0);

    CurrKeyName:=GetKeyNameW(CurrentKBDTable, (Buffer^ and $FF));
    if CurrKeyNamenil then
      WriteFile(FileHandle, CurrKeyName^, lstrlenW(CurrKeyName)*2, _Writed, 0)
                        else
      begin
        cVK:=GetVirtualCodeFromScanCode(CurrentKBDTable, Buffer^ and $FF);
        WriteFile(FileHandle, cVK, SizeOf(cVK), _Writed, 0);
      end;
    WriteFile(FileHandle, NewLine, 4, _Writed, 0);
    Inc(Buffer);
   end;
  CloseHandle(FileHandle); 
end;

Функция принимает три параметра: указатель на буфер, в который был закружен файл лога, размер буфера и имя файла, в который надо сохранить расшифровку. Функция в цикле обрабатывает каждый скан-код, если у скан-кода нет названия, функция получает виртуальный код (виртуальный код в этом случае будет равен ASCII коду символа). Размер одного элемента в буфере равен двум байтам. Сам скан-код содержится в младшем байте каждого элемента. Флаг, говорящий о том нажата была клавиша или отпущена, содержится в старшем байте каждого элемента.
   ВНИМАНИЕ! Результирующий файл содержит юникодовые строки, поэтому с помощью обычного блокнота этот файл посмотреть нельзя. Файл можно посмотреть только с помощью редактора поддерживающего UNICODE например, EmEditor, NotePad++ и т.д.
   Что же касается преобразования скан-кодов в текст, дополнительно надо будет вести список нажатых клавиш и конвертировать их во флаги модификаторы, если клавиши являются модифицирующими (SHIFT, CTRL и ALT). Задачу конвертирования скан-кодов в символы предоставляю вам. Дополнительная информация находится в файле kbd.h из комплекта WDK (или DDK).
Скачать программу для расшифровки скан-кодов и её исходники

Комментарии

  1. йоулопукки
    20 июля, 2013 | 19:23

    C;
    color: #000000;
    font: bold 11px tahoma, verdana, geneva, lucida, ‘lucida grande’, arial, helvetica, sans-serif;

  2. bzvbzv
    28 октября, 2014 | 13:14
  3. Isaev
    16 декабря, 2014 | 11:20

    Получается, чтобы считывать нажатие ‘J’ нужно загрузить одну dll, а для считывания нажатия ‘О’ другую, хотя это одна и та же клавиша?

  4. 10 января, 2015 | 16:47

    ! от http://www.sidorochev.ru привет