1 /* 2 * PROJECT: ReactOS api tests 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Test for Japanese IME conversion 5 * COPYRIGHT: Copyright 2022 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) 6 */ 7 8 #include <windows.h> 9 #include <windowsx.h> 10 #include <imm.h> 11 #include <wine/test.h> 12 13 /* 14 * We emulate some keyboard typing on dialog box and watch the conversion of Japanese IME. 15 * This program needs Japanese environment and Japanese IME. 16 * Tested on Japanese WinXP and Japanese Win10. 17 */ 18 19 #define INTERVAL 300 20 #define WM_PRESS_KEY_COMPLETE (WM_USER + 100) 21 22 /* The test entry structure */ 23 typedef struct tagTEST_ENTRY 24 { 25 const UINT *pKeys; 26 UINT cKeys; 27 const void *pvResult; 28 INT cWM_IME_ENDCOMPOSITION; 29 } TEST_ENTRY, *PTEST_ENTRY; 30 31 // The Japanese word "テスト" conversion in Romaji 32 static const UINT s_keys1[] = 33 { 34 'T', 'E', 'S', 'U', 'T', 'O', VK_SPACE, VK_RETURN 35 }; 36 // The Japanese word "調査員" conversion in Romaji 37 static const UINT s_keys2[] = 38 { 39 'C', 'H', 'O', 'U', 'S', 'A', 'I', 'N', 'N', VK_SPACE, VK_RETURN 40 }; 41 42 #ifdef UNICODE 43 #define AorW(a, w) w 44 #else 45 #define AorW(a, w) a 46 #endif 47 48 /* The test entries */ 49 static const TEST_ENTRY s_entries[] = 50 { 51 // "テスト" 52 { s_keys1, _countof(s_keys1), AorW("\x83\x65\x83\x58\x83\x67", L"\x30C6\x30B9\x30C8"), 1 }, 53 // "調査員" 54 { s_keys2, _countof(s_keys2), AorW("\x92\xB2\x8D\xB8\x88\xF5", L"\x8ABF\x67FB\x54E1"), 1 }, 55 }; 56 57 static INT s_iEntry = 0; 58 static INT s_cWM_IME_ENDCOMPOSITION = 0; 59 static WNDPROC s_fnOldEditWndProc = NULL; 60 61 #ifdef UNICODE 62 static LPSTR WideToAnsi(INT nCodePage, LPCWSTR pszWide) 63 { 64 static CHAR s_sz[512]; 65 WideCharToMultiByte(nCodePage, 0, pszWide, -1, s_sz, _countof(s_sz), NULL, NULL); 66 return s_sz; 67 } 68 #endif 69 70 /* The window procedure for textbox to watch the IME conversion */ 71 static LRESULT CALLBACK 72 EditWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 73 { 74 switch (uMsg) 75 { 76 case WM_IME_ENDCOMPOSITION: 77 { 78 const TEST_ENTRY *entry = &s_entries[s_iEntry]; 79 HIMC hIMC; 80 LONG cbResult, cbBuffer; 81 LPTSTR pszResult; 82 83 /* Check conversion results of composition string */ 84 hIMC = ImmGetContext(hwnd); 85 cbResult = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); 86 trace("cbResult: %ld\n", cbResult); 87 if (cbResult > 0) /* Ignore zero string */ 88 { 89 ok(hIMC != NULL, "hIMC was NULL\n"); 90 ++s_cWM_IME_ENDCOMPOSITION; 91 92 cbBuffer = cbResult + sizeof(WCHAR); 93 pszResult = (LPTSTR)calloc(cbBuffer, sizeof(BYTE)); /* Zero-fill */ 94 ok(pszResult != NULL, "pszResult was NULL\n"); 95 ImmGetCompositionString(hIMC, GCS_RESULTSTR, pszResult, cbBuffer); 96 #ifdef UNICODE 97 trace("%s\n", WideToAnsi(CP_ACP, (LPTSTR)pszResult)); 98 #else 99 trace("%s\n", (LPTSTR)pszResult); 100 #endif 101 ok(lstrcmp(pszResult, (LPTSTR)entry->pvResult) == 0, "pszResult differs\n"); 102 free(pszResult); 103 } 104 105 ImmReleaseContext(hwnd, hIMC); 106 } 107 break; 108 } 109 110 return CallWindowProc(s_fnOldEditWndProc, hwnd, uMsg, wParam, lParam); 111 } 112 113 /* Timer IDs */ 114 #define STAGE_1 10001 115 #define STAGE_2 10002 116 #define STAGE_3 10003 117 #define STAGE_4 10004 118 #define STAGE_5 10005 119 120 /* WM_INITDIALOG */ 121 static BOOL OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) 122 { 123 /* Subclass the textbox to watch the IME conversion */ 124 HWND hEdt1 = GetDlgItem(hwnd, edt1); 125 s_fnOldEditWndProc = (WNDPROC)SetWindowLongPtr(hEdt1, GWLP_WNDPROC, (LONG_PTR)EditWindowProc); 126 127 /* Go to first stage */ 128 SetTimer(hwnd, STAGE_1, INTERVAL, 0); 129 return TRUE; 130 } 131 132 /* WM_COMMAND */ 133 static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) 134 { 135 switch (id) 136 { 137 case IDOK: 138 case IDCANCEL: 139 EndDialog(hwnd, id); 140 break; 141 } 142 } 143 144 /* Emulate keyboard typing */ 145 static VOID PressKey(UINT vk) 146 { 147 INPUT inputs[2]; 148 ZeroMemory(inputs, sizeof(inputs)); 149 inputs[0].type = INPUT_KEYBOARD; 150 inputs[0].ki.wVk = vk; 151 inputs[1].type = INPUT_KEYBOARD; 152 inputs[1].ki.wVk = vk; 153 inputs[1].ki.dwFlags = KEYEVENTF_KEYUP; 154 SendInput(_countof(inputs), inputs, sizeof(INPUT)); 155 } 156 157 /* WM_TIMER */ 158 static void OnTimer(HWND hwnd, UINT id) 159 { 160 HIMC hIMC; 161 INT i; 162 const TEST_ENTRY *entry = &s_entries[s_iEntry]; 163 static DWORD dwOldConversion, dwOldSentence; 164 165 KillTimer(hwnd, id); 166 167 switch (id) 168 { 169 case STAGE_1: 170 /* Check focus. See WM_INITDIALOG return code. */ 171 ok(GetFocus() == GetDlgItem(hwnd, edt1), "GetFocus() was %p\n", GetFocus()); 172 173 hIMC = ImmGetContext(hwnd); 174 ok(hIMC != NULL, "hIMC was NULL"); 175 if (hIMC) 176 { 177 /* Open the IME */ 178 ImmSetOpenStatus(hIMC, TRUE); 179 /* Save the IME conversion status */ 180 ImmGetConversionStatus(hIMC, &dwOldConversion, &dwOldSentence); 181 /* Modify the IME conversion status */ 182 ImmSetConversionStatus(hIMC, 183 IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN | IME_CMODE_NATIVE, 184 IME_SMODE_SINGLECONVERT); 185 186 ImmReleaseContext(hwnd, hIMC); 187 } 188 /* Initialize the counter */ 189 s_cWM_IME_ENDCOMPOSITION = 0; 190 /* Go to next stage */ 191 SetTimer(hwnd, STAGE_2, INTERVAL, NULL); 192 break; 193 194 case STAGE_2: 195 /* Emulate keyboard typing */ 196 for (i = 0; i < entry->cKeys; ++i) 197 { 198 PressKey(entry->pKeys[i]); 199 } 200 /* Wait for message queue processed */ 201 PostMessage(hwnd, WM_PRESS_KEY_COMPLETE, 0, 0); 202 break; 203 204 case STAGE_3: 205 /* Revert the IME conversion status */ 206 hIMC = ImmGetContext(hwnd); 207 ok(hIMC != NULL, "hIMC was NULL"); 208 if (hIMC) 209 { 210 ImmSetConversionStatus(hIMC, dwOldConversion, dwOldSentence); 211 ImmReleaseContext(hwnd, hIMC); 212 } 213 /* Go to next stage */ 214 SetTimer(hwnd, STAGE_4, INTERVAL, NULL); 215 break; 216 217 case STAGE_4: 218 /* Check the counter */ 219 ok_int(s_cWM_IME_ENDCOMPOSITION, entry->cWM_IME_ENDCOMPOSITION); 220 if (s_cWM_IME_ENDCOMPOSITION < entry->cWM_IME_ENDCOMPOSITION) 221 { 222 skip("Some tests were skipped.\n"); 223 } 224 225 /* Go to next test entry */ 226 ++s_iEntry; 227 if (s_iEntry == _countof(s_entries)) 228 PostMessage(hwnd, WM_CLOSE, 0, 0); /* No more entry */ 229 else 230 SetTimer(hwnd, STAGE_1, INTERVAL, NULL); 231 break; 232 } 233 } 234 235 /* Dialog procedure */ 236 static INT_PTR CALLBACK 237 DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 238 { 239 switch (uMsg) 240 { 241 HANDLE_MSG(hwnd, WM_INITDIALOG, OnInitDialog); 242 HANDLE_MSG(hwnd, WM_COMMAND, OnCommand); 243 HANDLE_MSG(hwnd, WM_TIMER, OnTimer); 244 245 case WM_PRESS_KEY_COMPLETE: 246 /* Message queue is processed. Go to next stage. */ 247 SetTimer(hwnd, STAGE_3, INTERVAL, NULL); 248 break; 249 } 250 return 0; 251 } 252 253 #ifdef UNICODE 254 START_TEST(JapanImeConvTestW) 255 #else 256 START_TEST(JapanImeConvTestA) 257 #endif 258 { 259 /* Is the system Japanese? */ 260 if (PRIMARYLANGID(GetSystemDefaultLangID()) != LANG_JAPANESE) 261 { 262 skip("This testcase is for Japanese only.\n"); 263 return; 264 } 265 266 /* Is IMM enabled? */ 267 if (!GetSystemMetrics(SM_IMMENABLED)) 268 { 269 skip("SM_IMMENABLED is OFF.\n"); 270 return; 271 } 272 273 /* Check the current keyboard layout is IME */ 274 if (!ImmIsIME(GetKeyboardLayout(0))) 275 { 276 skip("The IME keyboard layout was not default\n"); 277 return; 278 } 279 280 DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(1), NULL, DialogProc); 281 282 if (s_iEntry < _countof(s_entries)) 283 skip("Some tests were skipped.\n"); 284 } 285