diff --git a/modules/rostests/apitests/imm32/CMakeLists.txt b/modules/rostests/apitests/imm32/CMakeLists.txt index 0b428e03bbe..6af5bb17cda 100644 --- a/modules/rostests/apitests/imm32/CMakeLists.txt +++ b/modules/rostests/apitests/imm32/CMakeLists.txt @@ -10,11 +10,12 @@ list(APPEND SOURCE ImmIsUIMessage.c JapanImeConvTestA.c JapanImeConvTestW.c + KLID.c testlist.c resource.rc) add_executable(imm32_apitest ${SOURCE}) target_link_libraries(imm32_apitest wine ${PSEH_LIB}) set_module_type(imm32_apitest win32cui) -add_importlibs(imm32_apitest imm32 msvcrt user32 kernel32 ntdll) +add_importlibs(imm32_apitest advapi32 imm32 msvcrt user32 kernel32 ntdll) add_rostests_file(TARGET imm32_apitest) diff --git a/modules/rostests/apitests/imm32/KLID.c b/modules/rostests/apitests/imm32/KLID.c new file mode 100644 index 00000000000..bb95cfb1600 --- /dev/null +++ b/modules/rostests/apitests/imm32/KLID.c @@ -0,0 +1,199 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Test for Keyboard Layout ID (KLID), HKL, and registry + * COPYRIGHT: Copyright 2024 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ + +#include "precomp.h" + +#include +#include +#include + +typedef enum tagHKL_TYPE +{ + HKL_TYPE_PURE = 0, + HKL_TYPE_SPECIAL = 1, + HKL_TYPE_IME = 2, + HKL_TYPE_CHIMERA = 3, +} HKL_TYPE; + +static HKL_TYPE GetHKLType(HKL hKL) +{ + /* 0xEXXXYYYY: An IME HKL. EXXX is an IME keyboard. YYYY is a language */ + if (IS_IME_HKL(hKL)) + return HKL_TYPE_IME; + + /* 0xFXXXYYYY: A special HKL. XXX is a special ID. YYYY is a language */ + if (IS_SPECIAL_HKL(hKL)) + return HKL_TYPE_SPECIAL; + + /* 0xXXXXXXXX: The keyboard layout and language is the same value */ + if (LOWORD(hKL) == HIWORD(hKL)) + return HKL_TYPE_PURE; + + /* 0xXXXXYYYY: XXXX is a keyboard. YYYY is a language */ + return HKL_TYPE_CHIMERA; +} + +static HKEY OpenKeyboardLayouts(void) +{ + HKEY hKey = NULL; + RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", + 0, KEY_READ, &hKey); + return hKey; +} + +static DWORD KLIDFromSpecialHKL(HKL hKL) +{ + WCHAR szName[16], szLayoutId[16]; + HKEY hkeyLayouts, hkeyKLID; + LSTATUS error; + DWORD dwSpecialId, dwLayoutId, cbValue, dwKLID = 0; + + hkeyLayouts = OpenKeyboardLayouts(); + ok(hkeyLayouts != NULL, "hkeyLayouts was NULL\n"); + + dwSpecialId = SPECIALIDFROMHKL(hKL); + + /* Search from "Keyboard Layouts" registry key */ + for (DWORD dwIndex = 0; dwIndex < 1000; ++dwIndex) + { + error = RegEnumKeyW(hkeyLayouts, dwIndex, szName, _countof(szName)); + szName[_countof(szName) - 1] = UNICODE_NULL; /* Avoid buffer overrun */ + if (error != ERROR_SUCCESS) + break; + + error = RegOpenKeyExW(hkeyLayouts, szName, 0, KEY_READ, &hkeyKLID); + if (error != ERROR_SUCCESS) + break; + + cbValue = sizeof(szLayoutId); + error = RegQueryValueExW(hkeyKLID, L"Layout Id", NULL, NULL, (LPBYTE)szLayoutId, &cbValue); + szLayoutId[_countof(szLayoutId) - 1] = UNICODE_NULL; /* Avoid buffer overrun */ + if (error != ERROR_SUCCESS) + { + RegCloseKey(hkeyKLID); + continue; + } + + dwLayoutId = wcstoul(szLayoutId, NULL, 16); + RegCloseKey(hkeyKLID); + if (dwLayoutId == dwSpecialId) /* Found */ + { + dwKLID = wcstoul(szName, NULL, 16); + break; + } + } + + RegCloseKey(hkeyLayouts); + return dwKLID; +} + +static DWORD KLIDFromHKL(HKL hKL) +{ + HKL_TYPE type = GetHKLType(hKL); + + trace("type: %d\n", type); + switch (type) + { + case HKL_TYPE_PURE: + case HKL_TYPE_CHIMERA: + return HIWORD(hKL); + + case HKL_TYPE_SPECIAL: + return KLIDFromSpecialHKL(hKL); + + case HKL_TYPE_IME: + return HandleToUlong(hKL); + } + + return 0; +} + +static void Test_KLID(DWORD dwKLID, HKL hKL) +{ + WCHAR szKLID[16], szValue[MAX_PATH]; + LSTATUS error; + DWORD dwValue, cbValue; + HKEY hkeyKLID, hkeyLayouts; + HKL_TYPE type; + + hkeyLayouts = OpenKeyboardLayouts(); + ok(hkeyLayouts != NULL, "hkeyLayouts was NULL\n"); + + StringCchPrintfW(szKLID, _countof(szKLID), L"%08lX", dwKLID); + RegOpenKeyExW(hkeyLayouts, szKLID, 0, KEY_READ, &hkeyKLID); + ok(hkeyKLID != NULL, "hkeyKLID was NULL\n"); + + error = RegQueryValueExW(hkeyKLID, L"Layout File", NULL, NULL, NULL, NULL); + ok_long(error, ERROR_SUCCESS); + + type = GetHKLType(hKL); + + if (type == HKL_TYPE_IME) + { + ok_long(dwKLID, HandleToUlong(hKL)); + error = RegQueryValueExW(hkeyKLID, L"IME File", NULL, NULL, NULL, NULL); + ok_long(error, ERROR_SUCCESS); + } + + if (type == HKL_TYPE_SPECIAL) + { + cbValue = sizeof(szValue); + error = RegQueryValueExW(hkeyKLID, L"Layout Id", NULL, NULL, (LPBYTE)&szValue, &cbValue); + ok_long(error, ERROR_SUCCESS); + + dwValue = wcstoul(szValue, NULL, 16); + ok_long(dwValue, SPECIALIDFROMHKL(hKL)); + } + + RegCloseKey(hkeyKLID); + RegCloseKey(hkeyLayouts); +} + +static void Test_HKL(HKL hKL) +{ + DWORD dwKLID; + + ok(hKL != NULL, "hKL was NULL\n"); + + dwKLID = KLIDFromHKL(hKL); + trace("dwKLID 0x%08lX, hKL %p\n", dwKLID, hKL); + + Test_KLID(dwKLID, hKL); +} + +START_TEST(KLID) +{ + HKL *phKLs; + INT iKL, cKLs; + + cKLs = GetKeyboardLayoutList(0, NULL); + trace("cKLs: %d\n", cKLs); + if (!cKLs) + { + skip("cKLs was zero\n"); + return; + } + + phKLs = calloc(cKLs, sizeof(HKL)); + if (!phKLs) + { + skip("!phKLs\n"); + return; + } + + ok_int(GetKeyboardLayoutList(cKLs, phKLs), cKLs); + + for (iKL = 0; iKL < cKLs; ++iKL) + { + trace("---\n"); + trace("phKLs[%d]: %p\n", iKL, phKLs[iKL]); + Test_HKL(phKLs[iKL]); + } + + free(phKLs); +} diff --git a/modules/rostests/apitests/imm32/testlist.c b/modules/rostests/apitests/imm32/testlist.c index 51005448c3d..a39c480f085 100644 --- a/modules/rostests/apitests/imm32/testlist.c +++ b/modules/rostests/apitests/imm32/testlist.c @@ -8,6 +8,7 @@ extern void func_imcc(void); extern void func_ImmEnumInputContext(void); extern void func_ImmGetImeInfoEx(void); extern void func_ImmIsUIMessage(void); +extern void func_KLID(void); extern void func_JapanImeConvTestA(void); extern void func_JapanImeConvTestW(void); @@ -19,6 +20,7 @@ const struct test winetest_testlist[] = { "ImmEnumInputContext", func_ImmEnumInputContext }, { "ImmGetImeInfoEx", func_ImmGetImeInfoEx }, { "ImmIsUIMessage", func_ImmIsUIMessage }, + { "KLID", func_KLID }, { "JapanImeConvTestA", func_JapanImeConvTestA }, { "JapanImeConvTestW", func_JapanImeConvTestW }, { 0, 0 }