如何创建键盘列表。从 HKL 中提取 KLID?
How to create a list of Keyboards. Extract KLID from HKL?
Windows 10/C++/Win32
我需要列出 'installed' 个键盘。
我的'list'的组件需要包含:
- 键盘的 HKL。
- 键盘的 KLID。
- 从以下位置获得的注册表值的值:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts
- 示例:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts[=12=]000409
- 值:布局文件
- 值:布局文本
我知道枚举键盘的唯一方法是通过 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'。
例如:
- 使用 MKLCS 创建自定义键盘美国。
- 安装它。
- 'KLID' 将是 'A0000409'。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\a0000409
- HKL 将为 0xF0C00409(在
GetKeyboardLayoutList()
生成的 HKL 列表中返回)。
- 为了用
LoadKeyboardLayout()
加载键盘,需要 'A0000409'。
- 似乎无法从 F0C00409 创建 A0000409。
- 我还创建了自己的没有 MKLCS 的键盘布局。
- 我随便给它起的名字00060409.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts[=18=]060409
- 它的 HKL 是 FFFE0409(在
GetKeyboardLayoutList()
生成的 HKL 列表中返回)。
- 似乎无法从 FFFE0409 创建 00060409。
综上所述,如何从 HKL 获得 KLID?
或者,还有其他方法可以创建我的已安装键盘列表吗?
//=========================================== =====================
11/25/2020 添加。
谢谢丽塔。
似乎 GetKeyboardLayoutList()
创建了一个 'Loaded' 键盘布局列表。
- 系统加载的键盘布局,0x0409409,....
- 通过 MKLCS 安装安装的键盘布局。
似乎在以下注册表项中定义的任何键盘都将在启动时加载。
HKEY_CURRENT_USER\Keyboard Layout
注意 Preload 和 Substitute 值。
(请注意,注册表中还有许多其他 'Keyboard Layout' 密钥,所以我
不确定 HKEY_CURRENT_USER\Keyboard Layout
是否是定义
PreLoad - 替补。
HKEY_USERS\.DEFAULT\Keyboard Layout
HKEY_USERS\S-1-5-18
(我的用户帐户)
HKEY_LOCAL_MACHINE\SYSTEM\Keyboard Layout
)
所以在我看来,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。
- 加载现有键盘以进行测试。 (您可以基于它修改或完全创建您自己的。)
- 有效并测试键盘以确保其按预期工作。然后构建DLL和安装包。
- 运行 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。
Windows 10/C++/Win32
我需要列出 'installed' 个键盘。
我的'list'的组件需要包含:
- 键盘的 HKL。
- 键盘的 KLID。
- 从以下位置获得的注册表值的值:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts
- 示例:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts[=12=]000409
- 值:布局文件
- 值:布局文本
我知道枚举键盘的唯一方法是通过 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'。
例如:
- 使用 MKLCS 创建自定义键盘美国。
- 安装它。
- 'KLID' 将是 'A0000409'。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\a0000409
- HKL 将为 0xF0C00409(在
GetKeyboardLayoutList()
生成的 HKL 列表中返回)。 - 为了用
LoadKeyboardLayout()
加载键盘,需要 'A0000409'。 - 似乎无法从 F0C00409 创建 A0000409。
- 我还创建了自己的没有 MKLCS 的键盘布局。
- 我随便给它起的名字00060409.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts[=18=]060409
- 它的 HKL 是 FFFE0409(在
GetKeyboardLayoutList()
生成的 HKL 列表中返回)。 - 似乎无法从 FFFE0409 创建 00060409。
综上所述,如何从 HKL 获得 KLID?
或者,还有其他方法可以创建我的已安装键盘列表吗?
//=========================================== ===================== 11/25/2020 添加。
谢谢丽塔。
似乎 GetKeyboardLayoutList()
创建了一个 'Loaded' 键盘布局列表。
- 系统加载的键盘布局,0x0409409,....
- 通过 MKLCS 安装安装的键盘布局。
似乎在以下注册表项中定义的任何键盘都将在启动时加载。
HKEY_CURRENT_USER\Keyboard Layout
注意 Preload 和 Substitute 值。
(请注意,注册表中还有许多其他 'Keyboard Layout' 密钥,所以我
不确定 HKEY_CURRENT_USER\Keyboard Layout
是否是定义
PreLoad - 替补。
HKEY_USERS\.DEFAULT\Keyboard Layout
HKEY_USERS\S-1-5-18
(我的用户帐户)HKEY_LOCAL_MACHINE\SYSTEM\Keyboard Layout
)
所以在我看来,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。
- 加载现有键盘以进行测试。 (您可以基于它修改或完全创建您自己的。)
- 有效并测试键盘以确保其按预期工作。然后构建DLL和安装包。
- 运行 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。