shell32: Handle NULL return icon parameters in SHDefExtractIcon.
[wine] / dlls / shell32 / tests / appbar.c
1 /* Unit tests for appbars
2  *
3  * Copyright 2008 Vincent Povirk for CodeWeavers
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19
20 #include <assert.h>
21 #include <stdarg.h>
22
23 #include <windows.h>
24
25 #include "wine/test.h"
26
27 #define MSG_APPBAR WM_APP
28
29 static const CHAR testwindow_class[] = "testwindow";
30
31 static HMONITOR (WINAPI *pMonitorFromWindow)(HWND, DWORD);
32
33 typedef BOOL (*boolean_function)(void);
34
35 struct testwindow_info
36 {
37     HWND hwnd;
38     BOOL registered;
39     RECT desired_rect;
40     UINT edge;
41     RECT allocated_rect;
42 };
43
44 static struct testwindow_info windows[3];
45
46 static int expected_bottom;
47
48 static void testwindow_setpos(HWND hwnd)
49 {
50     struct testwindow_info* info = (struct testwindow_info*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
51     APPBARDATA abd;
52     BOOL ret;
53
54     ok(info != NULL, "got unexpected ABN_POSCHANGED notification\n");
55
56     if (!info || !info->registered)
57     {
58         return;
59     }
60
61     abd.cbSize = sizeof(abd);
62     abd.hWnd = hwnd;
63     abd.uEdge = info->edge;
64     abd.rc = info->desired_rect;
65     ret = SHAppBarMessage(ABM_QUERYPOS, &abd);
66     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
67     switch (info->edge)
68     {
69         case ABE_BOTTOM:
70             ok(info->desired_rect.top == abd.rc.top, "ABM_QUERYPOS changed top of rect from %i to %i\n", info->desired_rect.top, abd.rc.top);
71             abd.rc.top = abd.rc.bottom - (info->desired_rect.bottom - info->desired_rect.top);
72             break;
73         case ABE_LEFT:
74             ok(info->desired_rect.right == abd.rc.right, "ABM_QUERYPOS changed right of rect from %i to %i\n", info->desired_rect.top, abd.rc.top);
75             abd.rc.right = abd.rc.left + (info->desired_rect.right - info->desired_rect.left);
76             break;
77         case ABE_RIGHT:
78             ok(info->desired_rect.left == abd.rc.left, "ABM_QUERYPOS changed left of rect from %i to %i\n", info->desired_rect.top, abd.rc.top);
79             abd.rc.left = abd.rc.right - (info->desired_rect.right - info->desired_rect.left);
80             break;
81         case ABE_TOP:
82             ok(info->desired_rect.bottom == abd.rc.bottom, "ABM_QUERYPOS changed bottom of rect from %i to %i\n", info->desired_rect.top, abd.rc.top);
83             abd.rc.bottom = abd.rc.top + (info->desired_rect.bottom - info->desired_rect.top);
84             break;
85     }
86
87     ret = SHAppBarMessage(ABM_SETPOS, &abd);
88     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
89
90     info->allocated_rect = abd.rc;
91     MoveWindow(hwnd, abd.rc.left, abd.rc.top, abd.rc.right-abd.rc.left, abd.rc.bottom-abd.rc.top, TRUE);
92 }
93
94 static LRESULT CALLBACK testwindow_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
95 {
96     switch(msg)
97     {
98         case MSG_APPBAR:
99         {
100             switch(wparam)
101             {
102                 case ABN_POSCHANGED:
103                     testwindow_setpos(hwnd);
104                     break;
105             }
106             return 0;
107         }
108     }
109
110     return DefWindowProc(hwnd, msg, wparam, lparam);
111 }
112
113 /* process pending messages until a condition is true or 3 seconds pass */
114 static void do_events_until(boolean_function test)
115 {
116     MSG msg;
117     UINT_PTR timerid;
118     BOOL timedout=FALSE;
119
120     timerid = SetTimer(0, 0, 3000, NULL);
121
122     while (1)
123     {
124         while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
125         {
126             if (msg.hwnd == 0 && msg.message == WM_TIMER && msg.wParam == timerid)
127                 timedout = TRUE;
128             TranslateMessage(&msg);
129             DispatchMessageA(&msg);
130         }
131         if (timedout || test())
132             break;
133         WaitMessage();
134     }
135
136     KillTimer(0, timerid);
137 }
138
139 /* process any pending messages */
140 static void do_events(void)
141 {
142     MSG msg;
143
144     while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
145     {
146         TranslateMessage(&msg);
147         DispatchMessageA(&msg);
148     }
149 }
150
151 static BOOL no_appbars_intersect(void)
152 {
153     int i, j;
154     RECT rc;
155
156     for (i=0; i<2; i++)
157     {
158         for (j=i+1; j<3; j++)
159         {
160             if (windows[i].registered && windows[j].registered &&
161                 IntersectRect(&rc, &windows[i].allocated_rect, &windows[j].allocated_rect))
162                 return FALSE;
163         }
164     }
165     return TRUE;
166 }
167
168 static BOOL got_expected_bottom(void)
169 {
170     return (no_appbars_intersect() && windows[1].allocated_rect.bottom == expected_bottom);
171 }
172
173 static void register_testwindow_class(void)
174 {
175     WNDCLASSEXA cls;
176
177     ZeroMemory(&cls, sizeof(cls));
178     cls.cbSize = sizeof(cls);
179     cls.style = 0;
180     cls.lpfnWndProc = testwindow_wndproc;
181     cls.hInstance = NULL;
182     cls.hCursor = LoadCursor(0, IDC_ARROW);
183     cls.hbrBackground = (HBRUSH) COLOR_WINDOW;
184     cls.lpszClassName = testwindow_class;
185
186     RegisterClassExA(&cls);
187 }
188
189 #define test_window_rects(a, b) \
190     ok(!IntersectRect(&rc, &windows[a].allocated_rect, &windows[b].allocated_rect), \
191         "rectangles intersect (%i,%i,%i,%i)/(%i,%i,%i,%i)\n", \
192         windows[a].allocated_rect.left, windows[a].allocated_rect.top, windows[a].allocated_rect.right, windows[a].allocated_rect.bottom, \
193         windows[b].allocated_rect.left, windows[b].allocated_rect.top, windows[b].allocated_rect.right, windows[b].allocated_rect.bottom)
194
195 static void test_setpos(void)
196 {
197     APPBARDATA abd;
198     RECT rc;
199     int screen_width, screen_height;
200     BOOL ret;
201
202     screen_width = GetSystemMetrics(SM_CXSCREEN);
203     screen_height = GetSystemMetrics(SM_CYSCREEN);
204
205     /* create and register windows[0] */
206     windows[0].hwnd = CreateWindowExA(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
207         testwindow_class, testwindow_class, WS_POPUP|WS_VISIBLE, 0, 0, 0, 0,
208         NULL, NULL, NULL, NULL);
209     ok(windows[0].hwnd != NULL, "couldn't create window\n");
210     do_events();
211     abd.cbSize = sizeof(abd);
212     abd.hWnd = windows[0].hwnd;
213     abd.uCallbackMessage = MSG_APPBAR;
214     ret = SHAppBarMessage(ABM_NEW, &abd);
215     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
216
217     /* ABM_NEW should return FALSE if the window is already registered */
218     ret = SHAppBarMessage(ABM_NEW, &abd);
219     ok(ret == FALSE, "SHAppBarMessage returned %i\n", ret);
220     do_events();
221
222     /* dock windows[0] to the bottom of the screen */
223     windows[0].registered = TRUE;
224     windows[0].edge = ABE_BOTTOM;
225     windows[0].desired_rect.left = 0;
226     windows[0].desired_rect.right = screen_width;
227     windows[0].desired_rect.top = screen_height - 15;
228     windows[0].desired_rect.bottom = screen_height;
229     SetWindowLongPtr(windows[0].hwnd, GWLP_USERDATA, (LONG_PTR)&windows[0]);
230     testwindow_setpos(windows[0].hwnd);
231     do_events();
232
233     /* create and register windows[1] */
234     windows[1].hwnd = CreateWindowExA(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
235         testwindow_class, testwindow_class, WS_POPUP|WS_VISIBLE, 0, 0, 0, 0,
236         NULL, NULL, NULL, NULL);
237     ok(windows[1].hwnd != NULL, "couldn't create window\n");
238     abd.hWnd = windows[1].hwnd;
239     ret = SHAppBarMessage(ABM_NEW, &abd);
240     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
241
242     /* dock windows[1] to the bottom of the screen */
243     windows[1].registered = TRUE;
244     windows[1].edge = ABE_BOTTOM;
245     windows[1].desired_rect.left = 0;
246     windows[1].desired_rect.right = screen_width;
247     windows[1].desired_rect.top = screen_height - 10;
248     windows[1].desired_rect.bottom = screen_height;
249     SetWindowLongPtr(windows[1].hwnd, GWLP_USERDATA, (LONG_PTR)&windows[1]);
250     testwindow_setpos(windows[1].hwnd);
251
252     /* the windows are adjusted to they don't overlap */
253     do_events_until(no_appbars_intersect);
254     test_window_rects(0, 1);
255
256     /* make windows[0] larger, forcing windows[1] to move out of its way */
257     windows[0].desired_rect.top = screen_height - 20;
258     testwindow_setpos(windows[0].hwnd);
259     do_events_until(no_appbars_intersect);
260     test_window_rects(0, 1);
261
262     /* create and register windows[2] */
263     windows[2].hwnd = CreateWindowExA(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
264         testwindow_class, testwindow_class, WS_POPUP|WS_VISIBLE, 0, 0, 0, 0,
265         NULL, NULL, NULL, NULL);
266     ok(windows[2].hwnd != NULL, "couldn't create window\n");
267     do_events();
268
269     abd.hWnd = windows[2].hwnd;
270     ret = SHAppBarMessage(ABM_NEW, &abd);
271     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
272
273     /* dock windows[2] to the bottom of the screen */
274     windows[2].registered = TRUE;
275     windows[2].edge = ABE_BOTTOM;
276     windows[2].desired_rect.left = 0;
277     windows[2].desired_rect.right = screen_width;
278     windows[2].desired_rect.top = screen_height - 10;
279     windows[2].desired_rect.bottom = screen_height;
280     SetWindowLongPtr(windows[2].hwnd, GWLP_USERDATA, (LONG_PTR)&windows[2]);
281     testwindow_setpos(windows[2].hwnd);
282
283     do_events_until(no_appbars_intersect);
284     test_window_rects(0, 1);
285     test_window_rects(0, 2);
286     test_window_rects(1, 2);
287
288     /* move windows[2] to the right side of the screen */
289     windows[2].edge = ABE_RIGHT;
290     windows[2].desired_rect.left = screen_width - 15;
291     windows[2].desired_rect.right = screen_width;
292     windows[2].desired_rect.top = 0;
293     windows[2].desired_rect.bottom = screen_height;
294     testwindow_setpos(windows[2].hwnd);
295
296     do_events_until(no_appbars_intersect);
297     test_window_rects(0, 1);
298     test_window_rects(0, 2);
299     test_window_rects(1, 2);
300
301     /* move windows[1] to the top of the screen */
302     windows[1].edge = ABE_TOP;
303     windows[1].desired_rect.left = 0;
304     windows[1].desired_rect.right = screen_width;
305     windows[1].desired_rect.top = 0;
306     windows[1].desired_rect.bottom = 15;
307     testwindow_setpos(windows[1].hwnd);
308
309     do_events_until(no_appbars_intersect);
310     test_window_rects(0, 1);
311     test_window_rects(0, 2);
312     test_window_rects(1, 2);
313
314     /* move windows[1] back to the bottom of the screen */
315     windows[1].edge = ABE_BOTTOM;
316     windows[1].desired_rect.left = 0;
317     windows[1].desired_rect.right = screen_width;
318     windows[1].desired_rect.top = screen_height - 10;
319     windows[1].desired_rect.bottom = screen_height;
320     testwindow_setpos(windows[1].hwnd);
321
322     do_events_until(no_appbars_intersect);
323     test_window_rects(0, 1);
324     test_window_rects(0, 2);
325     test_window_rects(1, 2);
326
327     /* removing windows[0] will cause windows[1] to move down into its space */
328     expected_bottom = max(windows[0].allocated_rect.bottom, windows[1].allocated_rect.bottom);
329
330     abd.hWnd = windows[0].hwnd;
331     ret = SHAppBarMessage(ABM_REMOVE, &abd);
332     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
333     windows[0].registered = FALSE;
334     DestroyWindow(windows[0].hwnd);
335
336     do_events_until(got_expected_bottom);
337
338     ok(windows[1].allocated_rect.bottom == expected_bottom, "windows[1]'s bottom is %i, expected %i\n", windows[1].allocated_rect.bottom, expected_bottom);
339
340     test_window_rects(1, 2);
341
342     /* remove the other windows */
343     abd.hWnd = windows[1].hwnd;
344     ret = SHAppBarMessage(ABM_REMOVE, &abd);
345     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
346     windows[1].registered = FALSE;
347     DestroyWindow(windows[1].hwnd);
348
349     abd.hWnd = windows[2].hwnd;
350     ret = SHAppBarMessage(ABM_REMOVE, &abd);
351     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
352     windows[2].registered = FALSE;
353     DestroyWindow(windows[2].hwnd);
354 }
355
356 static void test_appbarget(void)
357 {
358     APPBARDATA abd;
359     HWND hwnd, foregnd, unset_hwnd;
360     UINT_PTR ret;
361
362     memset(&abd, 0xcc, sizeof(abd));
363     memset(&unset_hwnd, 0xcc, sizeof(unset_hwnd));
364     abd.cbSize = sizeof(abd);
365     abd.uEdge = ABE_BOTTOM;
366
367     hwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);
368     ok(hwnd == NULL || IsWindow(hwnd), "ret %p which is not a window\n", hwnd);
369     ok(abd.hWnd == unset_hwnd, "hWnd overwritten %p\n",abd.hWnd);
370
371     if (!pMonitorFromWindow)
372     {
373         win_skip("MonitorFromWindow is not available\n");
374     }
375     else
376     {
377         /* Presumably one can pass a hwnd with ABM_GETAUTOHIDEBAR to specify a monitor.
378            Pass the foreground window and check */
379         foregnd = GetForegroundWindow();
380         if(foregnd)
381         {
382             abd.hWnd = foregnd;
383             hwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);
384             ok(hwnd == NULL || IsWindow(hwnd), "ret %p which is not a window\n", hwnd);
385             ok(abd.hWnd == foregnd, "hWnd overwritten\n");
386             if(hwnd)
387             {
388                 HMONITOR appbar_mon, foregnd_mon;
389                 appbar_mon = pMonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
390                 foregnd_mon = pMonitorFromWindow(foregnd, MONITOR_DEFAULTTONEAREST);
391                 ok(appbar_mon == foregnd_mon, "Windows on different monitors\n");
392             }
393         }
394     }
395
396     memset(&abd, 0xcc, sizeof(abd));
397     abd.cbSize = sizeof(abd);
398     ret = SHAppBarMessage(ABM_GETTASKBARPOS, &abd);
399     if(ret)
400     {
401         ok(abd.hWnd == (HWND)0xcccccccc, "hWnd overwritten\n");
402         ok(abd.uEdge <= ABE_BOTTOM, "uEdge not returned\n");
403         ok(abd.rc.left != 0xcccccccc, "rc not updated\n");
404     }
405
406     return;
407 }
408
409 START_TEST(appbar)
410 {
411     HMODULE huser32;
412
413     huser32 = GetModuleHandleA("user32.dll");
414     pMonitorFromWindow = (void*)GetProcAddress(huser32, "MonitorFromWindow");
415
416     register_testwindow_class();
417
418     test_setpos();
419     test_appbarget();
420 }