如何创建键盘列表。从 HKL 中提取 KLID?

How to create a list of Keyboards. Extract KLID from HKL?

Windows 10/C++/Win32

我需要列出 'installed' 个键盘。

我的'list'的组件需要包含:

我知道枚举键盘的唯一方法是通过 GetKeyboardLayoutList(),其中 returns 一个 HKL 列表。

适用于 'standard' 键盘的方法(HKL 的 04090409、04070407,..)。

TCHAR Buffer[50];
TCHAR Buffer2[50];
HKL LoadedKeyboardLayout;

// Hkl: 04090409
_stprintf_s(Buffer, (sizeof(Buffer)/sizeof(TCHAR)), _T("%08X"), (((UINT)Hkl >> 16) & 0xFFFF));
// Buffer: "0000409"
LoadedKeyboardLayout = LoadKeyboardLayout(Buffer, (UINT)0);
// It Loads

ActivateKeyboardLayout(LoadedKeyboardLayout, KLF_SETFORPROCESS);
// It Activates

GetKeyboardLayoutName(Buffer2); 
// Buffer2: "00000409"

// I can now fish the registry HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts[=10=]000409

当 'device identifier' 不是 0000 时,这不起作用。

来自 LoadKeyboardLayout() 文档:

The name of the input locale identifier to load. This name is a string composed of the hexadecimal value of the Language Identifier (low word) and a device identifier (high word). For example, U.S. English has a language identifier of 0x0409, so the primary U.S. English layout is named "00000409". Variants of U.S. English layout (such as the Dvorak layout) are named "00010409", "00020409", and so on.

如果创建 'Custom' 键盘,我发现无法从自定义键盘 HKL 获得 'device identifier'。

例如:

综上所述,如何从 HKL 获得 KLID?

或者,还有其他方法可以创建我的已安装键盘列表吗?

//=========================================== ===================== 11/25/2020 添加。

谢谢丽塔。

似乎 GetKeyboardLayoutList() 创建了一个 'Loaded' 键盘布局列表。

似乎在以下注册表项中定义的任何键盘都将在启动时加载。

(请注意,注册表中还有许多其他 'Keyboard Layout' 密钥,所以我 不确定 HKEY_CURRENT_USER\Keyboard Layout 是否是定义 PreLoad - 替补。

所以在我看来,Rita 的示例毫无疑问是有效的,因为她的 HKEY_CURRENT_USER\Keyboard 布局包含以下条目: 预加载: 1 d0010804

替补: d0010804 a0000804

这些条目可能是由 MKLCS 安装程序放在那里的? 或者通过设置->时间和语言->点击首选语言->选项->添加键盘来添加键盘

来自 ActivateKeyboardLayout() 文档:

The input locale identifier must have been loaded by a previous call to the LoadKeyboardLayout function.

因为 a0000804(HKL:F0C00804)实际上已经加载 ActivateKeyboardLayout() 有效。

因为我的 KLID: 00060409 没有在任何 X\Keyboard 布局预加载和替换中被引用 我必须实际调用 LoadKeyBoardLayout(L"00060409") 才能使其出现在 GetKeyboardList() HKL 中。

再次感谢丽塔。

how does one obtain a KLID from an HKL?

似乎没有开箱即用的直接方法来实现它。

解决方法是检索 HKL 列表,然后通过 HKL 激活布局,然后获取其 KLID。以下是示例:

int cnt = GetKeyboardLayoutList(sizeof(hklArr)/sizeof(hklArr[0]), hklArr);
if(cnt > 0)
{
    printf("keyboard list: \n");
    for (UINT i = 0; i < cnt; i++)
    {
        printf("%x\n", (LONG_PTR)hklArr[i]);
        if (ActivateKeyboardLayout(hklArr[i], KLF_SETFORPROCESS))
        {
            WCHAR pName[KL_NAMELENGTH];
            if (GetKeyboardLayoutName(pName))
            {
                wprintf(L"layout name (KLID): %s\n", pName);
            }
        }
    }
}

结果如下:

更新:

更新 2:分享我的创建和安装步骤。

我使用 Keyboard Layout Creator 1.4。

  1. 加载现有键盘以进行测试。 (您可以基于它修改或完全创建您自己的。)

  1. 有效并测试键盘以确保其按预期工作。然后构建DLL和安装包。

  1. 运行 setup.exe 由第2步生成。安装完成后,您将在注册表中看到相关的预加载键盘布局项。

没有安全的方法可以做到这一点,因为 it is not documented and can be changed after Windows updates

正确的算法已发布here

这是我目前在 Windows 10 版本 21H2(2021 年 11 月更新)上运行的代码:

// Returns KLID string of size KL_NAMELENGTH
// Same as GetKeyboardLayoutName but for any HKL
// https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-language-pack-default-values
BOOL GetKLIDFromHKL(HKL hkl, _Out_writes_(KL_NAMELENGTH) LPWSTR pwszKLID)
{
    bool succeded = false;

    if ((HIWORD(hkl) & 0xf000) == 0xf000) // deviceId contains layoutId
    {
        WORD layoutId = HIWORD(hkl) & 0x0fff;

        HKEY key;
        CHECK_EQ(::RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SYSTEM\CurrentControlSet\Control\Keyboard Layouts", &key), ERROR_SUCCESS);

        DWORD index = 0;
        while (::RegEnumKeyW(key, index, pwszKLID, KL_NAMELENGTH) == ERROR_SUCCESS)
        {
            WCHAR layoutIdBuffer[MAX_PATH] = {};
            DWORD layoutIdBufferSize = sizeof(layoutIdBuffer);
            if (::RegGetValueW(key, pwszKLID, L"Layout Id", RRF_RT_REG_SZ, nullptr, layoutIdBuffer, &layoutIdBufferSize) == ERROR_SUCCESS)
            {
                if (layoutId == std::stoul(layoutIdBuffer, nullptr, 16))
                {
                    succeded = true;
                    DBGPRINT("Found KLID 0x%ls by layoutId=0x%04x", pwszKLID, layoutId);
                    break;
                }
            }
            ++index;
        }
        CHECK_EQ(::RegCloseKey(key), ERROR_SUCCESS);
    }
    else
    {
        WORD langId = LOWORD(hkl);

        // deviceId overrides langId if set
        if (HIWORD(hkl) != 0)
            langId = HIWORD(hkl);

        std::swprintf(pwszKLID, KL_NAMELENGTH, L"%08X", langId);
        succeded = true;

        DBGPRINT("Found KLID 0x%ls by langId=0x%04x", pwszKLID, langId);
    }

    return succeded;
}

您还可以使用此代码在 LAYOUTORTIPPROFILE.szId 中使用 KLID 枚举布局配置文件:

typedef struct tagLAYOUTORTIPPROFILE {
    DWORD dwProfileType;
    LANGID langid;
    CLSID clsid;
    GUID guidProfile;
    GUID catid;
    DWORD dwSubstituteLayout;
    DWORD dwFlags;
    WCHAR szId[MAX_PATH];
} LAYOUTORTIPPROFILE;

// Flags used in LAYOUTORTIPPROFILE::dwProfileType
#define LOTP_INPUTPROCESSOR 1
#define LOTP_KEYBOARDLAYOUT 2

// Flags used in LAYOUTORTIPPROFILE::dwFlags.
#define LOT_DEFAULT 0x0001
#define LOT_DISABLED 0x0002

std::vector<LAYOUTORTIPPROFILE> EnumLayoutProfiles()
{
    // http://archives.miloush.net/michkap/archive/2008/09/29/8968315.html
    // https://docs.microsoft.com/en-us/windows/win32/tsf/enumenabledlayoutortip
    typedef UINT(WINAPI* EnumEnabledLayoutOrTipFunc)(LPCWSTR pszUserReg, LPCWSTR pszSystemReg, LPCWSTR pszSoftwareReg, LAYOUTORTIPPROFILE* pLayoutOrTipProfile, UINT uBufLength);
    static EnumEnabledLayoutOrTipFunc EnumEnabledLayoutOrTip = reinterpret_cast<EnumEnabledLayoutOrTipFunc>(::GetProcAddress(::LoadLibraryA("input.dll"), "EnumEnabledLayoutOrTip"));

    if (!EnumEnabledLayoutOrTip)
        return {};

    const UINT count = EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr, nullptr, 0);

    std::vector<LAYOUTORTIPPROFILE> layouts;
    layouts.resize(count);

    const UINT written = EnumEnabledLayoutOrTip(nullptr, nullptr, nullptr, layouts.data(), count);

    CHECK_EQ(count, written);

    return layouts;
}

布局的LAYOUTORTIPPROFILE.szId字符串格式为:

<LangID>:<KLID>

文本服务配置文件 (IME) 的字符串格式为:

<LangID>:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

更多信息here