Файл winhook/winhook.cpp
// ================================================ // Приложение WINHOOK // Простейший руссификатор клавиатуры // для Microsoft Windows // Работает совместно с DLL-библиотекой kbhook.dll // ================================================
#define STRICT #include <windows.h>
#include <windowsx.h>
#include <dos.h>
#include <mem.h>
#include "kbhook.hpp"
// ---------------------------------------------------- // Прототипы функций // ----------------------------------------------------
extern "C" void WINAPI _export SetKbHook(HWND hwnd);
extern "C" void WINAPI _export RemoveKbHook(void);
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// ---------------------------------------------------- // Глобальные переменные // ----------------------------------------------------
char const szClassName[] = "WINHOOKAppClass"; char const szWindowTitle[] = "WINHOOK Application";
TEXTMETRIC tm; int cxChar, cyChar; RECT rc; static BOOL bCyrillic = FALSE;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Можно запускать только одну копию приложения if(hPrevInstance) return 0;
// Инициализация копии приложения if(!InitApp(hInstance)) return FALSE;
// Получаем координаты окна Desktop. // Это окно занимает всю поверхность экрана // и на нем расположены все остальные окна GetWindowRect(GetDesktopWindow(), &rc);
// Создаем временное окно с толстой // рамкой для изменения размера, но без // заголовка и системного меню. // При создании окна указываем произвольные // размеры окна и произвольное расположение hwnd = CreateWindow( szClassName, szWindowTitle, WS_POPUPWINDOW | WS_THICKFRAME, 100, 100, 100, 100, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
// Передвигаем окно в правый нижний // угол экрана и делаем его самым // верхним, т. е. это окно будет // всегда находиться над другими окнами SetWindowPos(hwnd, HWND_TOPMOST, rc.right - cxChar * 15, rc.bottom - cyChar * 3, cxChar * 10, cyChar * 2, 0);
// Отображаем окно в новом месте ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg);
} return msg.wParam; }
// ===================================== // Функция InitApp // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
memset(&wc, 0, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { // Устанавливаем перехватчики SetKbHook(hwnd);
// Получаем контекст отображения hdc = GetDC(hwnd);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);
// Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth;
// Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc);
return 0; }
// Для обеспечения возможности перемещения // окна, не имеющего заголовка, встраиваем // свой обработчик сообщения WM_NCHITTEST case WM_NCHITTEST: { long lRetVal;
// Вызываем функцию DefWindowProc и проверяем // возвращаемое ей значение lRetVal = DefWindowProc(hwnd, msg, wParam, lParam);
// Если курсор мыши находится на одном из // элементов толстой рамки, предназначенной // для изменения размера окна, возвращаем // неизмененное значение, полученное от // функции DefWindowProc if(lRetVal == HTLEFT lRetVal == HTRIGHT lRetVal == HTTOP lRetVal == HTBOTTOM lRetVal == HTBOTTOMRIGHT lRetVal == HTTOPRIGHT lRetVal == HTTOPLEFT lRetVal == HTBOTTOMLEFT) { return lRetVal; }
// В противном случае возвращаем значение // HTCAPTION, которое соответствует // заголовку окна. else { return HTCAPTION; } }
case WM_DESTROY: { // Перед завершением работы приложения // удаляем перехватчики RemoveKbHook();
PostQuitMessage(0);
return 0; }
// Это сообщение приходит от DLL-библиотеки // при переключении раскладки клавиатуры case WM_KBHOOK: { // Получаем флаг раскладки bCyrillic = (BOOL)wParam;
// Выдаем звуковой сигнал MessageBeep(0);
// Перерисовываем окно приложения InvalidateRect(hwnd, NULL, FALSE);
return 0; }
case WM_PAINT: { BYTE szBuf[10]; RECT rc;
// Получаем контекст отображения hdc = BeginPaint(hwnd, &ps);
// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Получаем координаты и размер окна GetClientRect(hwnd, &rc);
// В зависимости от состояния флага раскладки // клавиатуры выбираем надпись для // отображения в окне if(bCyrillic) lstrcpy(szBuf, (LPCSTR)"CYRILLIC");
else lstrcpy(szBuf, (LPCSTR)"DEFAULT ");
// Выводим надпись в центре окна DrawText(hdc, (LPSTR)szBuf, 8, &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);
EndPaint(hwnd, &ps);
} } return DefWindowProc(hwnd, msg, wParam, lParam);
}
После запуска приложения функция WinMain определяет размеры окна DeskTop, которые равны размеру экрана, и создает главное окно приложения в виде временного окна с толстой рамкой для изменения размера без заголовка и системного меню (такое окно имеет стиль WS_POPUPWINDOW | WS_THICKFRAME).
Для того чтобы сделать окно "непотопляемым", а заодно и изменить его размеры, мы вызываем функцию SetWindowPos , передав ей в качестве второго параметра константу HWND_TOPMOST:
SetWindowPos(hwnd, HWND_TOPMOST, rc.right - cxChar * 15, rc.bottom - cyChar * 3, cxChar * 10, cyChar * 2, 0);
Функция SetWindowPos позволяет изменить размеры и расположение окна относительно экрана и относительно других окон:
BOOL WINAPI SetWindowPos( HWND hwnd, // идентификатор окна HWND hwndInsertAfter, // расположение окна int x, // горизонтальное положение int y, // вертикальное положение int cx, // ширина int cy, // высота UINT fuFlags);
// флаги расположения окна
Для параметра hwndInsertAfter, определяющего расположение окна относительно других окон, можно использовать следующие значения:
Значение | Описание |
HWND_BOTTOM | Окно следует расположить под другими окнами |
HWND_TOP | Окно будет расположено над другими окнами |
HWND_TOPMOST | Окно следует расположить над всеми другими окнами, имеющими расположение HWND_TOPMOST |
HWND_NOTOPMOST | Окно будет расположено над всеми HWND_TOP-окнами, но под окном, имеющим расположение HWND_TOPMOST |
Параметр fuFlags может принимать следующие значения:
Значение | Описание |
SWP_DRAWFRAME | Следует нарисовать рамку, определенную в классе окна |
SWP_HIDEWINDOW | Окно будет скрыто |
SWP_NOACTIVATE | Окно не будет активизировано |
SWP_NOMOVE | Окно не будет перемещаться, при указании этого флага параметры x и y игнорируются |
SWP_NOSIZE | Окно не будет изменять свои размеры, параметры cx и cy игнорируются |
SWP_NOREDRAW | Не следует выполнять перерисовку окна. После перемещения приложение должно перерисовать окно самостоятельно |
SWP_NOZORDER | Не следует изменять расположение окна относительно других окон, параметр hwndInsertAfter игнорируется |
SWP_SHOWWINDOW | Отобразить окно |
Функция главного окна приложения во время обработки сообщения WM_CREATE вызывает функцию SetKbHook, которая определена в созданной нами для этого приложения DLL-библиотеке kbhook.dll (исходные тексты этой библиотеки будут приведены ниже). Функция SetKbHook устанавливает два фильтра - типа WH_KEYBOARD и WH_GETMESSAGE. В качестве параметра этой функции передается идентификатор главного окна приложения WINHOOK. Когда пользователь переключает раскладку клавиатуры, DLL-библиотека, пользуясь этим идентификатором, пришлет в функцию окна приложения WINHOOK сообщение с кодом WM_KBHOOK.
Затем обработчик сообщения WM_CREATE получает контекст отображения и определяет метрику системного шрифта с фиксированной шириной букв, который будет использоваться для отображения используемой раскладки клавиатуры в главном окне приложения.
В функции главного окна предусмотрен обработчик сообщения WM_NCHITTEST, позволяющий перемещать окно, не имеющее заголовка. Мы уже использовали аналогичный прием в приложении TMCLOCK, описанном в 11 томе "Библиотеки системного программиста".
Перед завершением работы приложение WINHOOK удаляет установленные ранее фильтры, для чего при обработке сообщения WM_DESTROY вызывается функция RemoveKbHook, определенная в DLL-библиотеке kbhook.dll.
Обработчик сообщения WM_KBHOOK, определенного в нашем приложении и в DLL-библиотеке, определяет номер используемой раскладки клавиатуры через параметр wParam. Полученное значение записывается в переменную флагов раскладки bCyrillic. Если этот флаг сброшен, используется стандартная раскладка клавиатуры, если установлен - дополнительная с символами кириллицы.
После определения раскладки обработчик сообщения WM_KBHOOK выдает звуковой сигнал и перерисовывает главное окно приложения, вызывая функцию InvalidateRect.
Перерисовку окна выполняет обработчик сообщения WM_PAINT. С помощью функции DrawText он пишет название раскладки клавиатуры (DEFAULT или CYRILLIC) в центре главного окна приложения.
Сообщение WM_KBHOOK определено в include-файле winhook.hpp (листинг 3.10).