1 /*
2 * PROJECT: ReactOS API tests
3 * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+)
4 * PURPOSE: debugging and analysis of message states
5 * COPYRIGHT: Copyright 2019-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6 */
7
8 #include "precomp.h"
9 #include "undocuser.h"
10 #include "winxx.h"
11 #include <imm.h>
12 #include <strsafe.h>
13
14 #define MAX_MSGS 512
15
16 static MSG s_Msgs[MAX_MSGS];
17 static UINT s_cMsgs = 0;
18 static CHAR s_prefix[16] = "";
19 static HWND s_hMainWnd = NULL;
20 static HWND s_hwndEdit = NULL;
21 static HWND s_hImeWnd = NULL;
22 static WNDPROC s_fnOldEditWndProc = NULL;
23 static WNDPROC s_fnOldImeWndProc = NULL;
24
MsgDumpPrintf(LPCSTR fmt,...)25 static void MsgDumpPrintf(LPCSTR fmt, ...)
26 {
27 static char s_szText[1024];
28 va_list va;
29 va_start(va, fmt);
30 StringCbVPrintfA(s_szText, sizeof(s_szText), fmt, va);
31 trace("%s", s_szText);
32 va_end(va);
33 }
34 #define MSGDUMP_TPRINTF MsgDumpPrintf
35 #define MSGDUMP_PREFIX s_prefix
36 #include "msgdump.h" /* msgdump.h needs MSGDUMP_TPRINTF and MSGDUMP_PREFIX */
37
MD_build_prefix(void)38 static void MD_build_prefix(void)
39 {
40 DWORD Flags = InSendMessageEx(NULL);
41 INT i = 0;
42
43 if (Flags & ISMEX_CALLBACK)
44 s_prefix[i++] = 'C';
45 if (Flags & ISMEX_NOTIFY)
46 s_prefix[i++] = 'N';
47 if (Flags & ISMEX_REPLIED)
48 s_prefix[i++] = 'R';
49 if (Flags & ISMEX_SEND)
50 s_prefix[i++] = 'S';
51 if (i == 0)
52 s_prefix[i++] = 'P';
53
54 s_prefix[i++] = ':';
55 s_prefix[i++] = ' ';
56 s_prefix[i] = 0;
57 }
58
59 #define STAGE_1 1
60 #define STAGE_2 2
61 #define STAGE_3 3
62 #define STAGE_4 4
63 #define STAGE_5 5
64
findMessage(INT iMsg,HWND hwnd,UINT uMsg)65 static INT findMessage(INT iMsg, HWND hwnd, UINT uMsg)
66 {
67 if (iMsg == -1)
68 iMsg = 0;
69 for (; iMsg < s_cMsgs; ++iMsg)
70 {
71 if (s_Msgs[iMsg].message == uMsg && s_Msgs[iMsg].hwnd == hwnd)
72 return iMsg;
73 }
74 return -1;
75 }
76
77 typedef struct TEST_ENTRY
78 {
79 INT line;
80 LPCSTR name;
81 UINT iFound;
82 } TEST_ENTRY, *PTEST_ENTRY;
83
DoAnalyzeEntries(size_t nCount,PTEST_ENTRY pEntries)84 static VOID DoAnalyzeEntries(size_t nCount, PTEST_ENTRY pEntries)
85 {
86 size_t i;
87 for (i = 0; i < nCount - 1; ++i)
88 {
89 PTEST_ENTRY entry1 = &pEntries[i];
90 PTEST_ENTRY entry2 = &pEntries[i + 1];
91 ok(entry1->iFound < entry2->iFound,
92 "Line %d: message wrong order (%d >= %d): %s vs %s\n",
93 entry1->line, entry1->iFound, entry2->iFound,
94 entry1->name, entry2->name);
95 }
96 }
97
DoAnalyzeAllMessages(VOID)98 static VOID DoAnalyzeAllMessages(VOID)
99 {
100 size_t i;
101 TEST_ENTRY entries1[] =
102 {
103 { __LINE__, "WM_NCCREATE", findMessage(0, s_hMainWnd, WM_NCCREATE) },
104 { __LINE__, "WM_NCCALCSIZE", findMessage(0, s_hMainWnd, WM_NCCALCSIZE) },
105 { __LINE__, "WM_CREATE", findMessage(0, s_hMainWnd, WM_CREATE) },
106 { __LINE__, "WM_PARENTNOTIFY", findMessage(0, s_hMainWnd, WM_PARENTNOTIFY) },
107 { __LINE__, "WM_WINDOWPOSCHANGING", findMessage(0, s_hMainWnd, WM_WINDOWPOSCHANGING) },
108 { __LINE__, "WM_ACTIVATEAPP", findMessage(0, s_hMainWnd, WM_ACTIVATEAPP) },
109 { __LINE__, "WM_NCACTIVATE", findMessage(0, s_hMainWnd, WM_NCACTIVATE) },
110 { __LINE__, "WM_ACTIVATE", findMessage(0, s_hMainWnd, WM_ACTIVATE) },
111 { __LINE__, "WM_IME_SETCONTEXT", findMessage(0, s_hMainWnd, WM_IME_SETCONTEXT) },
112 { __LINE__, "WM_IME_NOTIFY", findMessage(0, s_hMainWnd, WM_IME_NOTIFY) },
113 { __LINE__, "WM_SETFOCUS", findMessage(0, s_hMainWnd, WM_SETFOCUS) },
114 { __LINE__, "WM_KILLFOCUS", findMessage(0, s_hMainWnd, WM_KILLFOCUS) },
115 };
116 INT iFound1 = entries1[_countof(entries1) - 1].iFound;
117 TEST_ENTRY entries2[] =
118 {
119 { __LINE__, "WM_IME_SETCONTEXT", findMessage(iFound1, s_hMainWnd, WM_IME_SETCONTEXT) },
120 { __LINE__, "WM_IME_SETCONTEXT", findMessage(iFound1, s_hwndEdit, WM_IME_SETCONTEXT) },
121 { __LINE__, "WM_SETFOCUS", findMessage(iFound1, s_hwndEdit, WM_SETFOCUS) },
122 { __LINE__, "WM_SHOWWINDOW", findMessage(iFound1, s_hMainWnd, WM_SHOWWINDOW) },
123 { __LINE__, "WM_WINDOWPOSCHANGING", findMessage(iFound1, s_hMainWnd, WM_WINDOWPOSCHANGING) },
124 { __LINE__, "WM_NCPAINT", findMessage(iFound1, s_hMainWnd, WM_NCPAINT) },
125 { __LINE__, "WM_ERASEBKGND", findMessage(iFound1, s_hMainWnd, WM_ERASEBKGND) },
126 { __LINE__, "WM_WINDOWPOSCHANGED", findMessage(iFound1, s_hMainWnd, WM_WINDOWPOSCHANGED) },
127 { __LINE__, "WM_SIZE", findMessage(iFound1, s_hMainWnd, WM_SIZE) },
128 { __LINE__, "WM_MOVE", findMessage(iFound1, s_hMainWnd, WM_MOVE) },
129 };
130 INT iFound2 = entries2[_countof(entries2) - 1].iFound;
131 TEST_ENTRY entries3[] =
132 {
133 { __LINE__, "WM_IME_KEYDOWN", findMessage(iFound2, s_hwndEdit, WM_IME_KEYDOWN) },
134 { __LINE__, "WM_KEYDOWN", findMessage(iFound2, s_hwndEdit, WM_KEYDOWN) },
135 { __LINE__, "WM_IME_KEYUP", findMessage(iFound2, s_hwndEdit, WM_IME_KEYUP) },
136 { __LINE__, "WM_CHAR", findMessage(iFound2, s_hwndEdit, WM_CHAR) },
137 { __LINE__, "WM_IME_CHAR", findMessage(iFound2, s_hwndEdit, WM_IME_CHAR) },
138 };
139 INT iFound3 = entries3[_countof(entries3) - 1].iFound;
140 TEST_ENTRY entries4[] =
141 {
142 { __LINE__, "WM_IME_NOTIFY", findMessage(iFound3, s_hwndEdit, WM_IME_NOTIFY) },
143 { __LINE__, "WM_IME_NOTIFY", findMessage(iFound3, s_hImeWnd, WM_IME_NOTIFY) },
144 { __LINE__, "WM_IME_SETCONTEXT", findMessage(iFound3, s_hwndEdit, WM_IME_SETCONTEXT) },
145 { __LINE__, "WM_IME_SETCONTEXT", findMessage(iFound3, s_hImeWnd, WM_IME_SETCONTEXT) },
146 };
147 INT iFound4 = entries4[_countof(entries4) - 1].iFound;
148 TEST_ENTRY entries5[] =
149 {
150 { __LINE__, "WM_DESTROY", findMessage(iFound4, s_hMainWnd, WM_DESTROY) },
151 { __LINE__, "WM_DESTROY", findMessage(iFound4, s_hwndEdit, WM_DESTROY) },
152 { __LINE__, "WM_NCDESTROY", findMessage(iFound4, s_hwndEdit, WM_NCDESTROY) },
153 { __LINE__, "WM_NCDESTROY", findMessage(iFound4, s_hMainWnd, WM_NCDESTROY) },
154 };
155 DoAnalyzeEntries(_countof(entries1), entries1);
156 DoAnalyzeEntries(_countof(entries2), entries2);
157 DoAnalyzeEntries(_countof(entries3), entries3);
158 //DoAnalyzeEntries(_countof(entries4), entries4); // No order
159 DoAnalyzeEntries(_countof(entries5), entries5);
160
161 ok(iFound1 < entries2[0].iFound, "%d vs %d\n", iFound1, entries2[0].iFound);
162 ok(iFound2 < entries3[0].iFound, "%d vs %d\n", iFound2, entries3[0].iFound);
163 ok(iFound3 < entries4[0].iFound, "%d vs %d\n", iFound3, entries4[0].iFound);
164 ok(iFound4 < entries5[0].iFound, "%d vs %d\n", iFound4, entries5[0].iFound);
165
166 for (i = 0; i < _countof(entries4); ++i)
167 {
168 ok(entries4[i].iFound != -1, "entries4[%d].iFound was -1\n", i);
169 }
170 }
171
172 static LRESULT CALLBACK
EditWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)173 EditWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
174 {
175 LRESULT ret;
176 MSG msg = { hwnd, uMsg, wParam, lParam };
177
178 /* Build s_prefix */
179 MD_build_prefix();
180
181 /* message dump */
182 MD_msgdump(hwnd, uMsg, wParam, lParam);
183
184 /* Add message */
185 if (s_cMsgs < MAX_MSGS)
186 s_Msgs[s_cMsgs++] = msg;
187
188 /* Do inner task */
189 ret = CallWindowProc(s_fnOldEditWndProc, hwnd, uMsg, wParam, lParam);
190
191 /* message return */
192 StringCbCopyA(s_prefix, sizeof(s_prefix), "R: ");
193 MD_msgresult(hwnd, uMsg, wParam, lParam, ret);
194
195 return ret;
196 }
197
198 static LRESULT CALLBACK
ImeWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)199 ImeWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
200 {
201 LRESULT ret;
202 MSG msg = { hwnd, uMsg, wParam, lParam };
203
204 /* Build s_prefix */
205 MD_build_prefix();
206
207 /* message dump */
208 MD_msgdump(hwnd, uMsg, wParam, lParam);
209
210 /* Add message */
211 if (s_cMsgs < MAX_MSGS)
212 s_Msgs[s_cMsgs++] = msg;
213
214 /* Do inner task */
215 ret = CallWindowProc(s_fnOldImeWndProc, hwnd, uMsg, wParam, lParam);
216
217 /* message return */
218 StringCbCopyA(s_prefix, sizeof(s_prefix), "R: ");
219 MD_msgresult(hwnd, uMsg, wParam, lParam, ret);
220
221 return ret;
222 }
223
224 static LRESULT CALLBACK
InnerWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)225 InnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
226 {
227 switch (uMsg)
228 {
229 case WM_NCCREATE:
230 s_hMainWnd = hwnd;
231 trace("s_hMainWnd: %p\n", s_hMainWnd);
232 break;
233 case WM_CREATE:
234 s_hwndEdit = CreateWindowW(L"EDIT", NULL, WS_CHILD | WS_VISIBLE,
235 0, 0, 100, 20, hwnd, NULL, GetModuleHandleW(NULL), NULL);
236 ok(s_hwndEdit != NULL, "s_hwndEdit was NULL\n");
237 trace("s_hwndEdit: %p\n", s_hwndEdit);
238 s_fnOldEditWndProc =
239 (WNDPROC)SetWindowLongPtrW(s_hwndEdit, GWLP_WNDPROC, (LONG_PTR)EditWindowProc);
240 SetFocus(s_hwndEdit);
241 PostMessageW(hwnd, WM_COMMAND, STAGE_1, 0);
242 break;
243 case WM_COMMAND:
244 switch (LOWORD(wParam))
245 {
246 case STAGE_1:
247 s_hImeWnd = ImmGetDefaultIMEWnd(hwnd);
248 ok(s_hImeWnd != NULL, "s_hImeWnd was NULL\n");
249 trace("s_hImeWnd: %p\n", s_hImeWnd );
250 s_fnOldImeWndProc = (WNDPROC)SetWindowLongPtrW(s_hImeWnd, GWLP_WNDPROC,
251 (LONG_PTR)ImeWindowProc);
252 PostMessageW(hwnd, WM_COMMAND, STAGE_2, 0);
253 break;
254 case STAGE_2:
255 PostMessageW(s_hwndEdit, WM_IME_KEYDOWN, 'A', 0);
256 PostMessageW(hwnd, WM_COMMAND, STAGE_3, 0);
257 break;
258 case STAGE_3:
259 PostMessageW(s_hwndEdit, WM_IME_KEYUP, 'A', 0);
260 PostMessageW(hwnd, WM_COMMAND, STAGE_4, 0);
261 break;
262 case STAGE_4:
263 PostMessageW(s_hwndEdit, WM_IME_CHAR, 'A', 0);
264 PostMessageW(hwnd, WM_COMMAND, STAGE_5, 0);
265 break;
266 case STAGE_5:
267 PostMessageW(hwnd, WM_CLOSE, 0, 0);
268 break;
269 }
270 break;
271 case WM_DESTROY:
272 PostQuitMessage(0);
273 break;
274 }
275
276 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
277 }
278
279 static LRESULT CALLBACK
WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)280 WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
281 {
282 LRESULT ret;
283 MSG msg = { hwnd, uMsg, wParam, lParam };
284
285 /* Build s_prefix */
286 MD_build_prefix();
287
288 /* message dump */
289 MD_msgdump(hwnd, uMsg, wParam, lParam);
290
291 /* Add message */
292 if (s_cMsgs < MAX_MSGS)
293 s_Msgs[s_cMsgs++] = msg;
294
295 /* Do inner task */
296 ret = InnerWindowProc(hwnd, uMsg, wParam, lParam);
297
298 /* message return */
299 StringCbCopyA(s_prefix, sizeof(s_prefix), "R: ");
300 MD_msgresult(hwnd, uMsg, wParam, lParam, ret);
301
302 return ret;
303 }
304
START_TEST(MessageStateAnalyzer)305 START_TEST(MessageStateAnalyzer)
306 {
307 WNDCLASSW wc;
308 HWND hwnd;
309 MSG msg;
310 static const WCHAR s_szName[] = L"MessageStateAnalyzer";
311
312 /* register window class */
313 ZeroMemory(&wc, sizeof(wc));
314 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
315 wc.lpfnWndProc = WindowProc;
316 wc.hInstance = GetModuleHandleW(NULL);
317 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
318 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
319 wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
320 wc.lpszClassName = s_szName;
321 if (!RegisterClassW(&wc))
322 {
323 skip("RegisterClassW failed.\n");
324 return;
325 }
326
327 /* create a window */
328 hwnd = CreateWindowW(s_szName, s_szName, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
329 CW_USEDEFAULT, CW_USEDEFAULT, 300, 200, NULL, NULL,
330 GetModuleHandleW(NULL), NULL);
331 if (!hwnd)
332 {
333 skip("CreateWindowW failed.\n");
334 return;
335 }
336
337 /* message loop */
338 while (GetMessageW(&msg, NULL, 0, 0))
339 {
340 TranslateMessage(&msg);
341 DispatchMessageW(&msg);
342 }
343
344 DoAnalyzeAllMessages();
345 }
346