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