Keyboard layout indicator next to the mouse text cursor

    Hello, Habr. Today I want to introduce for your consideration a small utility utility that will show the keyboard layout indicator next to the mouse text cursor.



    Intro


    Once upon a time (it seems, last Friday), about three years ago, there once was a certain free program (I won’t indicate the name, the author didn’t pay me for this year :)). She is now, only costs money. All its functionality was to show the current keyboard layout next to the mouse textthe cursor. The program sat in the tray, constantly displayed some popups, (it seems) broke into the Internet, got confused underfoot, devotedly looked into the eyes, etc. In general, a restless patient. I saw her, admired the idea and tried to use in everyday work. It seemed to me very uncomfortable for the reasons described above, and then I decided to write mine, with blackjack and ... hmm, without popups. Having looked at a little (much is impossible, in EULA it is written) the algorithm of its work, I found out that to achieve the desired effect, only a few API calls are used.

    Cursor


    So, for starters, let's figure out how to change the text cursor icon. MSDN tells us that this can be done by calling SetSystemCursor. The first parameter you need to pass the handle to the cursor, and the second to indicate which cursor we are changing - the main, busy cursor, text cursor, etc. In this particular case, the second parameter will be OCR_IBEAM, and now we will deal with the first. Having carefully read the article on SetSystemCursor (which I did not do for the first time), you can see that you cannot pass the handle received using the LoadCursor function as a handle to the cursor, because the system destroys the cursor passed to SetSystemCursor (commendable cleanliness, otherwise in other places). It also says in MSDN that this problem is solved by copying the cursor before passing it to SetSystemCursor using the CopyCursor function. Total

    HCURSOR hRuCur = LoadCursorA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDC_RUS));
    HCURSOR hRuCopy = CopyCursor(hRuCur);
    SetSystemCursor(hRuCopy, OCR_IBEAM);

    * This source code was highlighted with Source Code Highlighter.

    This code will replace the text mouse cursor (I-beam) with the cursor loaded from the IDC_RUS resource. And will replace in all applications.

    Layout


    How to change the cursor we found out. Now let's figure out how to determine the keyboard layout for the window, above which the mouse cursor is now. Let’s go from the end - we define the window above which the cursor is located. This is done using the following sequence of API calls: we call GetCursorPos to determine the current cursor coordinates, then we call WindowFromPoint with the received coordinates. Voila, we have the handle of the window. Why do we need him? :) To determine the keyboard layout in the window under the cursor. How to do it? Call GetKeyboardLayout, of course! Only here, for it, some obscure thread id is needed - the identifier of the thread in which the window is created. After a little digging into the adjacent sections of MSDN, you can find the GetWindowThreadProcessId function, which returns the id of the thread that created it using the well-known window handle. Puzzle assembled:

    POINT p;
    HWND wnd;
    HKL lay;
    DWORD dwThreadId;
    GetCursorPos(&p);
    wnd = WindowFromPoint(p);
    dwThreadId = GetWindowThreadProcessId(wnd, 0);
    lay = GetKeyboardLayout(dwThreadId);

    * This source code was highlighted with Source Code Highlighter.

    Keyboard layout codes can be found on MSDN if you search for a long time :) Or you can put the breakpoint on the last line and look at the lay values ​​for different layouts.

    Together


    Now we know all the parts of our future utility, you can put them together in a heap. Because my main motivation was to create an easy and very small program, I put all of the above code directly into WinMain and made it spin endlessly without the possibility of exiting the loop. And why, in fact? A full listing looks something like this:

    int __stdcall WinMain(__in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd )
    {
      HCURSOR hRuCur = LoadCursorA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDC_RUS));
      HCURSOR hEnCur = LoadCursorA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDC_ENG));
      if (!hEnCur || !hRuCur)
      {
        MessageBoxA(0, "Failed to load cursors. Terminating.", "Fatal error", MB_ICONERROR);
        return 0;
      }

      while (1)
      {
        HCURSOR hRuCopy;
        HCURSOR hEnCopy;
        HKL lay = 0;
        POINT p;
        HWND wnd;
        DWORD dwThreadId = 0;

        hRuCopy = CopyCursor(hRuCur);
        hEnCopy = CopyCursor(hEnCur);

        GetCursorPos(&p);
        wnd = WindowFromPoint(p);
        if (!IsWindow(wnd)) continue;

        dwThreadId = GetWindowThreadProcessId(wnd, 0);
        lay = GetKeyboardLayout(dwThreadId);

        if ((DWORD)lay == 0x4190419/*RU*/) SetSystemCursor(hRuCopy, OCR_IBEAM);
        if ((DWORD)lay == 0x4090409/*EN*/) SetSystemCursor(hEnCopy, OCR_IBEAM);

        Sleep(250);
      }

      return 0;
    }

    * This source code was highlighted with Source Code Highlighter.

    Project in VS


    To create a project in visual studio you need to select Win32 - Win32 Project - Empty project, add a cpp-file with the winmain function described above and add two cursor options - for systems with two layouts, of course :) You can take the cursors ready on the Internet, or you can draw them yourself right in the studio. But that is not all! After all, it was about a “small utility utility”, right? A monster in 70Kb does not fit this description :) So we climb under the hood and remove everything that is possible. Firstly, the project properties - Linker - Advanced - Entry point - WinMain. Secondly, the project properties - Linker - Command line - align: 16. Thirdly, you can still poke buttons to your taste. After recompilation, I got 3Kb, of which 1.5Kb are cursors. Maybe even less, I don’t know.

    Outro


    The code can be called "dirty", ugly, etc. However, it performs its function one hundred percent - the mouse cursor has now become more informative, no one throws out a crowd of unnecessary pop-ups in the tray, and priceless RAM is free to place the absolutely necessary Windows Help and Support service in it.

    Also popular now: