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
WideToAnsi(INT nCodePage,LPCWSTR pszWide)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
EditWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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 */
OnInitDialog(HWND hwnd,HWND hwndFocus,LPARAM lParam)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 */
OnCommand(HWND hwnd,int id,HWND hwndCtl,UINT codeNotify)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 */
PressKey(UINT vk)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 */
OnTimer(HWND hwnd,UINT id)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
DialogProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)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
START_TEST(JapanImeConvTestW)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