← Back to list

Make Win32-function `GetKeyboardLayoutName` work in Python

GetKeyboardLayoutName() should give the current KLID… but sometimes it fails. Here’s how we go from confusing docs to a working solution.

Oleksii Sylichenko · 2025-08-15 11:40 · 0 claps · 2.5 min read
#python #win32
Open on Medium ↗

Make Win32-function GetKeyboardLayoutName work in Python

Problem

Our Python + Win32 API program tracks the keyboard layout using:

GetKeyboardLayout(thread_id)

This function returns an HKL (handle to a keyboard layout).

We have several XML files describing different keyboard layouts. Each file is named after the KLID (keyboard layout identifier).

Examples:

  • US: HKL = 0x4090409, KLID = “00000409”
  • Ukrainian: HKL = 0x4220422, KLID = “00000422”
  • Ukrainian (Enhanced): HKL = -0xF57FBDE, KLID = “00020422”

Goal: Convert HKL KLID without hard-coded mapping, using only Win32 API.

What the documentation says

GetKeyboardLayout documentation suggests:

To get the KLID (keyboard layout ID) of the currently active HKL, call GetKeyboardLayoutName.

GetKeyboardLayoutName:

Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.

BOOL GetKeyboardLayoutNameW(
    [out] LPSTR pwszKLID
);

pwszKLID will be a copy of the string provided to the LoadKeyboardLayout function, unless layout substitution took place.

LoadKeyboardLayout syntax:

HKL LoadKeyboardLayoutW(
  [in] LPCWSTR pwszKLID,
  [in] UINT    Flags
);

Problem: LoadKeyboardLayout requires a KLID, but we only have an HKL.

ActivateKeyboardLayout seems promising:

HKL ActivateKeyboardLayout(
  [in] HKL  hkl,
  [in] UINT Flags
);

However, the docs note:

[in] hkl — The input locale identifier must have been loaded by a previous call to the LoadKeyboardLayout function.

Experiment

It works fine for standard layouts:

LoadKeyboardLayout("00000422")
ActivateKeyboardLayout(0x4220422)
print(GetKeyboardLayoutName())

✅ Output: “00000422” — correct.

But fails for enhanced layouts:

LoadKeyboardLayout("00020422")
ActivateKeyboardLayout(-0xF57FBDE)
print(GetKeyboardLayoutName())

❌ Output: default KLID — not the one we expect.

The hidden clue

Buried in the docs:

Since the keyboard layout can be dynamically changed, applications that cache information about the current keyboard layout should process the WM_INPUTLANGCHANGE message to be informed of changes in the input language.

Translation:

You need a Win32 window to receive system messages about layout changes.

Solution

  1. Create a hidden Win32 window in Python to be able to receive WM_INPUTLANGCHANGE.
  2. Send it a WM_INPUTLANGCHANGEREQUEST message with your HKL.
  3. Call GetKeyboardLayoutName after the change to retrieve the correct KLID.

Step 1 — Create a hidden window

import win32gui
hidden_window_hwnd: int = 0

def hidden_window_thread():
    wc = win32gui.WNDCLASS()
    wc.lpszClassName = "MyHiddenWindowClass"
    wc.hInstance = win32gui.GetModuleHandle(None)

    class_atom = win32gui.RegisterClass(wc)
    global hidden_window_hwnd
    hidden_window_hwnd = win32gui.CreateWindow(
        class_atom,
        "MyHiddenWindow",
        0, 0, 0, 0, 0, 0, 0, wc.hInstance, None
    )
    print(f"Hidden window created, hwnd = {hwnd}")
    win32gui.PumpMessages()  # run message loop

Documentation references:

If needed, you can add a message handler, but it’s not required:

from win32con import WM_DESTROY
from win32gui import PostQuitMessage, DefWindowProc

def wnd_proc(hwnd, msg, wparam, lparam):
    if WM_DESTROY == msg:
        PostQuitMessage(0)
    return DefWindowProc(hwnd, msg, wparam, lparam)

...
wc.lpfnWndProc = wnd_proc

Run the hidden window non-blocking:

from threading import Thread
...
Thread(target=hidden_window_thread, daemon=True).start()

Step 2 — Send the layout change request

For a given HKL, request the system to change the layout for the hidden window and then call GetKeyboardLayoutName():

from win32api import SendMessage, GetKeyboardLayoutName
from win32con import WM_INPUTLANGCHANGEREQUEST

INPUTLANGCHANGE_SYSCHARSET: int = 0x0001

def hkl_to_klid(hkl: int) -> str:
    SendMessage(hidden_window_hwnd,
        WM_INPUTLANGCHANGEREQUEST,
        INPUTLANGCHANGE_SYSCHARSET,
        hkl)
    return GetKeyboardLayoutName()

Alternatively, using PostMessage (asynchronous), the KLID can be handled in the window procedure:

from win32con import WM_INPUTLANGCHANGE, WM_DESTROY, \
    WM_INPUTLANGCHANGEREQUEST
from win32api import PostMessage, GetKeyboardLayoutName
from win32gui import PostQuitMessage, DefWindowProc

def wnd_proc(hwnd, msg, wparam, lparam):
    if WM_INPUTLANGCHANGE == msg:
        print(f"Language changed! KLID: {GetKeyboardLayoutName()}")
    elif WM_DESTROY == msg:
        PostQuitMessage(0)
    return DefWindowProc(hwnd, msg, wparam, lparam)

...
PostMessage(hidden_window_hwnd,
  WM_INPUTLANGCHANGEREQUEST,
  INPUTLANGCHANGE_SYSCHARSET,
  hkl)

Test Stand

The full test script on GitHub Gist:

  1. Creates a hidden window.
  2. Iterates through available layouts.
  3. Requests a layout change for that window.
  4. Reads and prints the KLID using GetKeyboardLayoutName.

References


메타데이터
post_id
452e320fca3b
slug
make-win32-function-getkeyboardlayoutname-work-in-python-452e320fca3b
url
https://medium.com/@asilichenko/make-win32-function-getkeyboardlayoutname-work-in-python-452e320fca3b
canonical_url
https://medium.com/@asilichenko/make-win32-function-getkeyboardlayoutname-work-in-python-452e320fca3b
author_url
https://medium.com/@asilichenko
status
ok
fetched_at
2026-07-18 05:34:24