Здравствуй, Хабр. Сегодня я хочу представить на твое рассмотрение маленькую утилиту-полезняшку, которая будет показывать индикатор раскладки клавиатуры рядом с мышиным текстовым курсором.

Давным-давно (кажется, в прошлую пятницу), года три назад, жила-была некая бесплатная программка (название указывать не буду, автор мне пиар не проплатил за этот год :)). Она и сейчас есть, только стоит денежку. Весь ее функционал заключался в показывании текущей раскладки клавиатуры рядом с мышиным текстовым курсором. Программа сидела в трее, постоянно выводила какие-то попапы, (кажется) ломилась в инет, путалась под ногами, преданно заглядывала в глаза и т.д. В общем, беспокойный пациент. Я увидел ее, восхитился идеей и попробовал использовать в повседневной работе. Мне она показалась очень неудобной по вышеописанным причинам и тогда я решил написать свою, с блэкджеком и… хм, без попапов. Подсмотрев немного (много нельзя, в EULA так написано) алгоритм ее работы, я выяснил, что для достижения необходимого эффекта используется всего несколько API-вызовов.
Итак, для начала разберемся, как сменить значок текстового курсора. MSDN подсказывает нам, что это можно сделать с помощью вызова SetSystemCursor. Первым параметром нужно передать хэндл на курсор, а вторым указать, какой именно курсор мы меняем — основной, курсор занятости, текстовый курсор и т.д. В данном конкретном случае второй параметр у нас будет OCR_IBEAM, а с первым сейчас будем разбираться. Внимательно прочитав статью по SetSystemCursor (чего я в первый раз не сделал), можно заметить, что в качестве хэндла на курсор нельзя передавать хэндл, полученный с помощью функции LoadCursor, т.к. система уничтожает переданный в SetSystemCursor курсор (похвальная чистоплотность, в других бы местах так). Там же в MSDN написано, что эта проблема решается копированием курсора перед передачей его в SetSystemCursor с помощью функции CopyCursor. Итого, если мы добавим в exe-шник ресурс с курсором, то использовать его можно так:
Этот код заменит текстовый курсор мыши (I-beam) на курсор, загруженный из ресурса IDC_RUS. Причем заменит во всех приложениях.
Как сменить курсор мы выяснили. Теперь разберемся, как определить раскладку клавиатуры для окна, над которым сейчас находится мышиный курсор. Зайдем с конца — определим окно, над которым находится курсор. Это делается с помощью такой последовательности API-вызовов: вызываем GetCursorPos для определения текущих координат курсора, затем вызываем WindowFromPoint с получеными координатами. Вуаля, хэндл окна у нас есть. Зачем он нам нужен? :) Чтобы определить раскладку клавиатуры в окне под курсором. Как это сделать? Вызвать GetKeyboardLayout, конечно же! Только вот для нее нужен какой-то малопонятный thread id — идентификатор потока, в котором создано окно. Немного покопавшись в смежных разделах MSDN, можно найти функцию GetWindowThreadProcessId, которая по известному хэндлу окна возвращает id потока, его создавшего. Паззл собрался:
Коды раскладок клавиатуры можно узнать в MSDN, если долго искать :) А можно поставить брякпоинт на последнюю строку и подсмотреть значения lay для разных раскладок.
Теперь мы знаем все части нашей будущей утилиты, можно собрать их в кучу. Т.к. основной моей мотивацией было создание легкой и очень маленькой программы, весь вышеописанный код я поместил прямо в WinMain и заставил его там крутиться до бесконечности без возможности выхода из цикла. А зачем, собственно? Полный листинг выглядит примерно так:
Для создания проекта в visual studio нужно выбрать Win32 — Win32 Project — Empty project, добавить cpp-файл с вышеописанной функцией winmain и добавить два варианта курсоров — для систем с двумя раскладками, естественно :) Курсоры можно взять готовые в инете, а можно нарисовать самому прямо в студии. Но это еще не все! Речь ведь шла о «маленькой утилите-полезняшке», так? А монстр в 70Кб никак не подходит под это описание :) Поэтому лезем под капот и убираем все что можно. Во-первых, свойства проекта — Linker — Advanced — Entry point — WinMain. Во-вторых, свойства проекта — Linker — Command line — align:16. В-третьих, можно еще потыкать кнопочки на ваш вкус. У меня после перекомпиляции получилось 3Кб, из которых 1.5Кб это курсоры. Может можно и еще меньше, не знаю.
Код вполне можно назвать «грязным», некрасивым и т.д. Однако, свою функцию он выполняет на все сто — мышиный курсор теперь стал более информативен, в трее никто не вываливает толпу ненужных попапов, а бесценная ОЗУ свободна для размещения в ней совершенно необходимой службы винды «Справка и поддержка».

Интро
Давным-давно (кажется, в прошлую пятницу), года три назад, жила-была некая бесплатная программка (название указывать не буду, автор мне пиар не проплатил за этот год :)). Она и сейчас есть, только стоит денежку. Весь ее функционал заключался в показывании текущей раскладки клавиатуры рядом с мышиным текстовым курсором. Программа сидела в трее, постоянно выводила какие-то попапы, (кажется) ломилась в инет, путалась под ногами, преданно заглядывала в глаза и т.д. В общем, беспокойный пациент. Я увидел ее, восхитился идеей и попробовал использовать в повседневной работе. Мне она показалась очень неудобной по вышеописанным причинам и тогда я решил написать свою, с блэкджеком и… хм, без попапов. Подсмотрев немного (много нельзя, в EULA так написано) алгоритм ее работы, я выяснил, что для достижения необходимого эффекта используется всего несколько API-вызовов.
Курсор
Итак, для начала разберемся, как сменить значок текстового курсора. MSDN подсказывает нам, что это можно сделать с помощью вызова SetSystemCursor. Первым параметром нужно передать хэндл на курсор, а вторым указать, какой именно курсор мы меняем — основной, курсор занятости, текстовый курсор и т.д. В данном конкретном случае второй параметр у нас будет OCR_IBEAM, а с первым сейчас будем разбираться. Внимательно прочитав статью по SetSystemCursor (чего я в первый раз не сделал), можно заметить, что в качестве хэндла на курсор нельзя передавать хэндл, полученный с помощью функции LoadCursor, т.к. система уничтожает переданный в SetSystemCursor курсор (похвальная чистоплотность, в других бы местах так). Там же в MSDN написано, что эта проблема решается копированием курсора перед передачей его в SetSystemCursor с помощью функции CopyCursor. Итого, если мы добавим в exe-шник ресурс с курсором, то использовать его можно так:
HCURSOR hRuCur = LoadCursorA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDC_RUS));<br>HCURSOR hRuCopy = CopyCursor(hRuCur);<br>SetSystemCursor(hRuCopy, OCR_IBEAM);<br><br>* This source code was highlighted with Source Code Highlighter.
Этот код заменит текстовый курсор мыши (I-beam) на курсор, загруженный из ресурса IDC_RUS. Причем заменит во всех приложениях.
Раскладка
Как сменить курсор мы выяснили. Теперь разберемся, как определить раскладку клавиатуры для окна, над которым сейчас находится мышиный курсор. Зайдем с конца — определим окно, над которым находится курсор. Это делается с помощью такой последовательности API-вызовов: вызываем GetCursorPos для определения текущих координат курсора, затем вызываем WindowFromPoint с получеными координатами. Вуаля, хэндл окна у нас есть. Зачем он нам нужен? :) Чтобы определить раскладку клавиатуры в окне под курсором. Как это сделать? Вызвать GetKeyboardLayout, конечно же! Только вот для нее нужен какой-то малопонятный thread id — идентификатор потока, в котором создано окно. Немного покопавшись в смежных разделах MSDN, можно найти функцию GetWindowThreadProcessId, которая по известному хэндлу окна возвращает id потока, его создавшего. Паззл собрался:
POINT p;<br>HWND wnd;<br>HKL lay;<br>DWORD dwThreadId;<br>GetCursorPos(&p);<br>wnd = WindowFromPoint(p);<br>dwThreadId = GetWindowThreadProcessId(wnd, 0);<br>lay = GetKeyboardLayout(dwThreadId);<br><br>* This source code was highlighted with Source Code Highlighter.
Коды раскладок клавиатуры можно узнать в MSDN, если долго искать :) А можно поставить брякпоинт на последнюю строку и подсмотреть значения lay для разных раскладок.
Все вместе
Теперь мы знаем все части нашей будущей утилиты, можно собрать их в кучу. Т.к. основной моей мотивацией было создание легкой и очень маленькой программы, весь вышеописанный код я поместил прямо в WinMain и заставил его там крутиться до бесконечности без возможности выхода из цикла. А зачем, собственно? Полный листинг выглядит примерно так:
int __stdcall WinMain(__in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd )<br>{<br> HCURSOR hRuCur = LoadCursorA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDC_RUS));<br> HCURSOR hEnCur = LoadCursorA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDC_ENG));<br> if (!hEnCur || !hRuCur)<br> {<br> MessageBoxA(0, "Failed to load cursors. Terminating.", "Fatal error", MB_ICONERROR);<br> return 0;<br> }<br><br> while (1)<br> {<br> HCURSOR hRuCopy;<br> HCURSOR hEnCopy;<br> HKL lay = 0;<br> POINT p;<br> HWND wnd;<br> DWORD dwThreadId = 0;<br><br> hRuCopy = CopyCursor(hRuCur);<br> hEnCopy = CopyCursor(hEnCur);<br><br> GetCursorPos(&p);<br> wnd = WindowFromPoint(p);<br> if (!IsWindow(wnd)) continue;<br><br> dwThreadId = GetWindowThreadProcessId(wnd, 0);<br> lay = GetKeyboardLayout(dwThreadId);<br><br> if ((DWORD)lay == 0x4190419/*RU*/) SetSystemCursor(hRuCopy, OCR_IBEAM);<br> if ((DWORD)lay == 0x4090409/*EN*/) SetSystemCursor(hEnCopy, OCR_IBEAM);<br><br> Sleep(250);<br> }<br><br> return 0;<br>}<br><br>* This source code was highlighted with Source Code Highlighter.
Проект в VS
Для создания проекта в visual studio нужно выбрать Win32 — Win32 Project — Empty project, добавить cpp-файл с вышеописанной функцией winmain и добавить два варианта курсоров — для систем с двумя раскладками, естественно :) Курсоры можно взять готовые в инете, а можно нарисовать самому прямо в студии. Но это еще не все! Речь ведь шла о «маленькой утилите-полезняшке», так? А монстр в 70Кб никак не подходит под это описание :) Поэтому лезем под капот и убираем все что можно. Во-первых, свойства проекта — Linker — Advanced — Entry point — WinMain. Во-вторых, свойства проекта — Linker — Command line — align:16. В-третьих, можно еще потыкать кнопочки на ваш вкус. У меня после перекомпиляции получилось 3Кб, из которых 1.5Кб это курсоры. Может можно и еще меньше, не знаю.
Аутро
Код вполне можно назвать «грязным», некрасивым и т.д. Однако, свою функцию он выполняет на все сто — мышиный курсор теперь стал более информативен, в трее никто не вываливает толпу ненужных попапов, а бесценная ОЗУ свободна для размещения в ней совершенно необходимой службы винды «Справка и поддержка».