Pull to refresh

Индикатор раскладки клавиатуры рядом с мышиным текстовым курсором

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



Интро


Давным-давно (кажется, в прошлую пятницу), года три назад, жила-была некая бесплатная программка (название указывать не буду, автор мне пиар не проплатил за этот год :)). Она и сейчас есть, только стоит денежку. Весь ее функционал заключался в показывании текущей раскладки клавиатуры рядом с мышиным текстовым курсором. Программа сидела в трее, постоянно выводила какие-то попапы, (кажется) ломилась в инет, путалась под ногами, преданно заглядывала в глаза и т.д. В общем, беспокойный пациент. Я увидел ее, восхитился идеей и попробовал использовать в повседневной работе. Мне она показалась очень неудобной по вышеописанным причинам и тогда я решил написать свою, с блэкджеком и… хм, без попапов. Подсмотрев немного (много нельзя, в 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Кб это курсоры. Может можно и еще меньше, не знаю.

Аутро


Код вполне можно назвать «грязным», некрасивым и т.д. Однако, свою функцию он выполняет на все сто — мышиный курсор теперь стал более информативен, в трее никто не вываливает толпу ненужных попапов, а бесценная ОЗУ свободна для размещения в ней совершенно необходимой службы винды «Справка и поддержка».
Tags:
Hubs:
+30
Comments32

Articles

Change theme settings