// ================================================
// DLL-библиотека kbhook.dll
// Устанавливает перехватчики на сообщения,
// поступающие от клавиатуры и на системную
// очередь сообщений.
// Если нажать подряд 3 раза клавишу <Control>,
// изменится раскладка клавиатуры
// ================================================

#define  STRICT
#include <windows.h>
#include "kbhook.hpp"

// -----------------------------------------------
// Глобальные переменные
// -----------------------------------------------

// Идентификатор модуля DLL-библиотеки
static HINSTANCE hInst;

// Идентификатор окна приложения, установившего
// перехватчики
static HWND hwndClient;

// Идентификаторы перехватчиков
static HHOOK hhook = 0;
static HHOOK hhookMsg = 0;

// Флаг переключения на русскую клавиатуру
static BOOL bCyrillic = FALSE;

// Флаг установки перехватчиков
static BOOL bHooked = FALSE;

// Счетчик 
static int nHotKeyCount = 0;

// Массив для записи состояния клавиатуры
BYTE aKeyStates[256];

// Указатели на таблицы перекодировки,
// которые будут загружены из ресурсов
static char far * lpXlatTable;
static char far * lpXlatTableCaps;

// Положение ресурсов в файле
static HRSRC   hResource;
static HRSRC   hResourceCaps;

// Идентификаторы таблиц перекодировки
static HGLOBAL hXlatTable;
static HGLOBAL hXlatTableCaps;

// -----------------------------------------------
// Прототипы функций
// -----------------------------------------------

extern "C"
LRESULT CALLBACK KbHookProc(int code,
  WPARAM wParam, LPARAM lParam);

extern "C"
LRESULT CALLBACK MsgHookProc(int code,
  WPARAM wParam, LPARAM lParam);

// ========================================================
// Функция LibMain
// Получает управление только один раз при
// загрузке DLL-библиотеки в память
// ========================================================

#pragma argsused
int CALLBACK
LibMain(HINSTANCE hInstance,
                  WORD wDataSegment,
                  WORD wHeapSize,
                  LPSTR lpszCmdLine)
{
  // После инициализации локальной области данных
  // функция LibEntry фиксирует сегмент данных.
  // Его необходимо расфиксировать.
  if(wHeapSize != 0)
    // Расфиксируем сегмент данных
    UnlockData(0);

  // Запоминаем идентификатор модуля DLL-библиотеки
  hInst = hInstance;

  // Определяем расположение ресурсов
  hResource   = FindResource(hInstance, "XlatTable", "XLAT");
  hResourceCaps = FindResource(hInstance, "XlatTableCaps", "XLAT");

  // Получаем идентификаторы ресурсов
  hXlatTable  = LoadResource(hInstance, hResource);
  hXlatTableCaps  = LoadResource(hInstance, hResourceCaps);

  // Фиксируем ресурсы в памяти, получая их адрес
  lpXlatTable = (char far *)LockResource(hXlatTable);
  lpXlatTableCaps = (char far *)LockResource(hXlatTableCaps);

  // Если адрес равен NULL, при загрузке или
  // фиксации одного из ресурсов произошла ошибка
  if(lpXlatTable == NULL || lpXlatTableCaps == NULL)
  {
    return(0);
  }

  // Выключаем клавишу <Caps Lock>. Для этого
  // получаем и записываем в массив  aKeyStates состояние
  // клавиатуры, затем изменяем состояние нужной нам
  // клавиши, используя ее виртуальный код как индекс
  GetKeyboardState(aKeyStates);
  aKeyStates[VK_CAPITAL] = 0;
  SetKeyboardState(aKeyStates);

  // Возвращаем 1. Это означает, что инициализация
  // DLL-библиотеки выполнена успешно
  return 1;
}

// ========================================================
// Функция WEP
// Получает управление только один раз при
// удалении DLL-библиотеки из памяти
// ========================================================

#pragma argsused
int CALLBACK
WEP(int bSystemExit)
{
  // Расфиксируем и освобождаем ресурсы
  UnlockResource(hXlatTable);
  FreeResource(hXlatTable);
  UnlockResource(hXlatTableCaps);
  FreeResource(hXlatTableCaps);

  return 1;
}

// ========================================================
// Функция SetKbHook
// Устанавливает системные перехватчики на сообщения,
// поступающие от клавиатуры, и на системную
// очередь сообщений
// ========================================================

extern "C"
void WINAPI _export SetKbHook(HWND hwnd)
{
  // Устанавливаем перехватчики только
  // в том случае, если они не были
  // установлены ранее 
  if(!bHooked)
  {
    // Установка перехватчика на сообщения,
    // поступающие от клавиатуры
    hhook = SetWindowsHookEx(WH_KEYBOARD,
      (HOOKPROC)KbHookProc,
      hInst, NULL); 

    // Установка перехватчика на системную
    // очередь сообщений
    hhookMsg = SetWindowsHookEx(WH_GETMESSAGE,
      (HOOKPROC)MsgHookProc,
      hInst, NULL);

    // Включаем флаг установки перехватчиков
    bHooked = TRUE;

    // Сохраняем идентификатор окна приложения,
    // установившего перехватчики
    hwndClient = hwnd;
  }
}

// ========================================================
// Функция RemoveKbHook
// Удаляет системные перехватчики
// ========================================================

extern "C"
void WINAPI _export RemoveKbHook(void)
{
  // Если перехватчики были установлены,
  // удаляем их
  if(bHooked)
  {
    UnhookWindowsHookEx(hhookMsg);
    UnhookWindowsHookEx(hhook);
  }
}

// ========================================================
// Функция KbHookProc
// Перехватчик сообщений от клавиатуры
// ========================================================

extern "C"
LRESULT CALLBACK KbHookProc(int code,
  WPARAM wParam, LPARAM lParam)
{
  // Проверка флага обработки сообщений. Если содержимое
  // параметра code меньше нуля, передаем сообщение
  // функции CallNextHookEx без изменений
  if(code < 0)
  {
    CallNextHookEx(hhook, code, wParam, lParam);
    return 0;
  }

  // Если пришло сообщение от клавиши <Control>,
  // проверяем, была ли эта клавиша нажата
  // три раза подряд
  if(wParam  == VK_CONTROL)
  { 
    if(!(HIWORD(lParam) & 0x8000))
    {
      nHotKeyCount++;

      // Если клавиша <Control> была нажата три
      // раза подряд, выдаем звуковой сигнал и
      // инвертируем флаг bCyrillic и посылаем
      // сообщение приложению, использующему
      // данную DLL-библиотеку
      if(nHotKeyCount == 3)
      {
        nHotKeyCount = 0;
        bCyrillic = ~bCyrillic;

        // Посылаем сообщение приложению, установившему
        // перехватчики. В качестве параметра wParam
        // сообщения передаем значение флага bCyrillic
        PostMessage(hwndClient, WM_KBHOOK,
          (WPARAM)bCyrillic, 0L);
      }
    }
  }

  // Если после клавиши <Control> была нажата любая
  // другая клавиша, сбрасываем счетчик
  else
  {
    nHotKeyCount = 0;
  }

  // Вызываем следующий в цепочке перехватчик
  return CallNextHookEx(hhook, code, wParam, lParam);
}

// ========================================================
// Функция KbHookProc
// Перехватчик для системной очереди сообщений
// ========================================================

extern "C"
LRESULT CALLBACK MsgHookProc(int code,
  WPARAM wParam, LPARAM lParam)
{
  LPMSG lpmsg;
  WPARAM wMsgParam;

  // Проверка флага обработки сообщений. Если содержимое
  // параметра code меньше нуля, передаем сообщение
  // функции CallNextHookEx без изменений
  if(code < 0)
  {
    CallNextHookEx(hhook, code, wParam, lParam);
    return 0;
  }

  // Получаем указатель на структуру MSG,
  // в которой находится перехваченное сообщение
  lpmsg = (LPMSG)lParam;

  // Запоминаем виртуальный код клавиши
  wMsgParam = lpmsg->wParam;

  // Сбрасываем флаг замены сообщения
  // WM_KEYDOWN на сообщение WM_CHAR
  BOOL bChange = FALSE;

  // Если перехвачено сообщение WM_KEYDOWN,
  // проверяем код виртуальной клавиши и при
  // необходимости выполняем замену сообщения
  if(lpmsg->message == WM_KEYDOWN)
  {
    // Замена выполняется только в режиме
    // русской клавиатуры
    if(bCyrillic)
    {
      // Если нажата клавиша, соответствующая
      // русской букве, включаем флаг bChange
      switch(wMsgParam)
      {
        // Проверяем "особые" буквы
        case 0xdb: // "Х"
        case 0xdd: // "Ъ"
        case 0xba: // "Ж"
        case 0xde: // "Э"
        case 0xbc: // "Б"
        case 0xbe: // "Ю"
        case 0xbf: // "Ў"
        {
          bChange = TRUE;
          break;
        }

        // Проверяем остальные буквы
        default:
        {
          if((lpmsg->wParam <= 0x5d && lpmsg->wParam > 0x2f))
            bChange = TRUE;
        }
      }

      // Если нажата клавиша, соответствующая русской
      // букве, выполняем замену сообщения WM_KEYDOWN на
      // сообщение WM_CHAR
      if(bChange)
      {
        // Делаем замену кода сообщения
        lpmsg->message = WM_CHAR;

        // Необходимо учитывать состояние клавиш
        // <Caps Lock> и <Shift>
        if(GetKeyState(VK_CAPITAL) & 0x1)
        {
          if(GetKeyState(VK_SHIFT) & 0x8000)
          {
            // Перекодировка по таблице строчных букв
            lpmsg->wParam =
              lpXlatTable[(lpmsg->wParam) & 0xff];
          }
          else
          {
            // Перекодировка по таблице прописных букв
            lpmsg->wParam =
              lpXlatTableCaps[(lpmsg->wParam) & 0xff];
          }
        }
        else
        {
          if(GetKeyState(VK_SHIFT) & 0x8000)
          {
            // Перекодировка по таблице прописных букв
            lpmsg->wParam = lpXlatTableCaps[(lpmsg->wParam) & 0xff];
          }
          else
          {
            // Перекодировка по таблице строчных букв
            lpmsg->wParam = lpXlatTable[(lpmsg->wParam) & 0xff];
          }
        }

        // Сбрасываем флаг замены
        bChange = FALSE;
      }
    }
  }

  // Передаем управление следующему в цепочке
  // перехватчику сообщений
  CallNextHookEx(hhook, code, wParam, lParam);

  return 0;
}
