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