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 <stdarg.h>
21 
22 #include <windows.h>
23 #include "shellapi.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 static HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(PWSTR*);
33 
34 typedef BOOL (*boolean_function)(void);
35 
36 struct testwindow_info
37 {
38     HWND hwnd;
39     BOOL registered;
40     BOOL to_be_deleted;
41     RECT desired_rect;
42     UINT edge;
43     RECT allocated_rect;
44 };
45 
46 static struct testwindow_info windows[3];
47 
48 static int expected_bottom;
49 
50 static void testwindow_setpos(HWND hwnd)
51 {
52     struct testwindow_info* info = (struct testwindow_info*)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
53     APPBARDATA abd;
54     BOOL ret;
55 
56     ok(info != NULL, "got unexpected ABN_POSCHANGED notification\n");
57 
58     if (!info || !info->registered)
59     {
60         return;
61     }
62 
63     if (info->to_be_deleted)
64     {
65         win_skip("Some Win95 and NT4 systems send messages to removed taskbars\n");
66         return;
67     }
68 
69     abd.cbSize = sizeof(abd);
70     abd.hWnd = hwnd;
71     abd.uEdge = info->edge;
72     abd.rc = info->desired_rect;
73     ret = SHAppBarMessage(ABM_QUERYPOS, &abd);
74     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
75     switch (info->edge)
76     {
77         case ABE_BOTTOM:
78             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);
79             abd.rc.top = abd.rc.bottom - (info->desired_rect.bottom - info->desired_rect.top);
80             break;
81         case ABE_LEFT:
82             ok(info->desired_rect.right == abd.rc.right, "ABM_QUERYPOS changed right of rect from %i to %i\n", info->desired_rect.right, abd.rc.right);
83             abd.rc.right = abd.rc.left + (info->desired_rect.right - info->desired_rect.left);
84             break;
85         case ABE_RIGHT:
86             ok(info->desired_rect.left == abd.rc.left, "ABM_QUERYPOS changed left of rect from %i to %i\n", info->desired_rect.left, abd.rc.left);
87             abd.rc.left = abd.rc.right - (info->desired_rect.right - info->desired_rect.left);
88             break;
89         case ABE_TOP:
90             ok(info->desired_rect.bottom == abd.rc.bottom, "ABM_QUERYPOS changed bottom of rect from %i to %i\n", info->desired_rect.bottom, abd.rc.bottom);
91             abd.rc.bottom = abd.rc.top + (info->desired_rect.bottom - info->desired_rect.top);
92             break;
93     }
94 
95     ret = SHAppBarMessage(ABM_SETPOS, &abd);
96     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
97 
98     info->allocated_rect = abd.rc;
99     MoveWindow(hwnd, abd.rc.left, abd.rc.top, abd.rc.right-abd.rc.left, abd.rc.bottom-abd.rc.top, TRUE);
100 }
101 
102 static LRESULT CALLBACK testwindow_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
103 {
104     switch(msg)
105     {
106         case MSG_APPBAR:
107         {
108             switch(wparam)
109             {
110                 case ABN_POSCHANGED:
111                     testwindow_setpos(hwnd);
112                     break;
113             }
114             return 0;
115         }
116     }
117 
118     return DefWindowProcA(hwnd, msg, wparam, lparam);
119 }
120 
121 /* process pending messages until a condition is true or 3 seconds pass */
122 static void do_events_until(boolean_function test)
123 {
124     MSG msg;
125     UINT_PTR timerid;
126     BOOL timedout=FALSE;
127 
128     timerid = SetTimer(0, 0, 3000, NULL);
129 
130     while (1)
131     {
132         while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
133         {
134             if (msg.hwnd == 0 && msg.message == WM_TIMER && msg.wParam == timerid)
135                 timedout = TRUE;
136             TranslateMessage(&msg);
137             DispatchMessageA(&msg);
138         }
139         if (timedout || test())
140             break;
141         WaitMessage();
142     }
143 
144     KillTimer(0, timerid);
145 }
146 
147 /* process any pending messages */
148 static void do_events(void)
149 {
150     MSG msg;
151 
152     while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
153     {
154         TranslateMessage(&msg);
155         DispatchMessageA(&msg);
156     }
157 }
158 
159 static BOOL no_appbars_intersect(void)
160 {
161     int i, j;
162     RECT rc;
163 
164     for (i=0; i<2; i++)
165     {
166         for (j=i+1; j<3; j++)
167         {
168             if (windows[i].registered && windows[j].registered &&
169                 IntersectRect(&rc, &windows[i].allocated_rect, &windows[j].allocated_rect))
170                 return FALSE;
171         }
172     }
173     return TRUE;
174 }
175 
176 static BOOL got_expected_bottom(void)
177 {
178     return (no_appbars_intersect() && windows[1].allocated_rect.bottom == expected_bottom);
179 }
180 
181 static void register_testwindow_class(void)
182 {
183     WNDCLASSEXA cls;
184 
185     ZeroMemory(&cls, sizeof(cls));
186     cls.cbSize = sizeof(cls);
187     cls.style = 0;
188     cls.lpfnWndProc = testwindow_wndproc;
189     cls.hInstance = NULL;
190     cls.hCursor = LoadCursorA(0, (LPSTR)IDC_ARROW);
191     cls.hbrBackground = (HBRUSH) COLOR_WINDOW;
192     cls.lpszClassName = testwindow_class;
193 
194     RegisterClassExA(&cls);
195 }
196 
197 #define test_window_rects(a, b) \
198     ok(!IntersectRect(&rc, &windows[a].allocated_rect, &windows[b].allocated_rect), \
199         "rectangles intersect %s / %s\n", wine_dbgstr_rect(&windows[a].allocated_rect), \
200         wine_dbgstr_rect(&windows[b].allocated_rect))
201 
202 static void test_setpos(void)
203 {
204     APPBARDATA abd;
205     RECT rc;
206     int screen_width, screen_height;
207     BOOL ret;
208     int org_bottom1;
209 
210     screen_width = GetSystemMetrics(SM_CXSCREEN);
211     screen_height = GetSystemMetrics(SM_CYSCREEN);
212 
213     /* create and register windows[0] */
214     windows[0].hwnd = CreateWindowExA(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
215         testwindow_class, testwindow_class, WS_POPUP|WS_VISIBLE, 0, 0, 0, 0,
216         NULL, NULL, NULL, NULL);
217     ok(windows[0].hwnd != NULL, "couldn't create window\n");
218     do_events();
219     abd.cbSize = sizeof(abd);
220     abd.hWnd = windows[0].hwnd;
221     abd.uCallbackMessage = MSG_APPBAR;
222     ret = SHAppBarMessage(ABM_NEW, &abd);
223     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
224 
225     /* ABM_NEW should return FALSE if the window is already registered */
226     ret = SHAppBarMessage(ABM_NEW, &abd);
227     ok(ret == FALSE, "SHAppBarMessage returned %i\n", ret);
228     do_events();
229 
230     /* dock windows[0] to the bottom of the screen */
231     windows[0].registered = TRUE;
232     windows[0].to_be_deleted = FALSE;
233     windows[0].edge = ABE_BOTTOM;
234     SetRect(&windows[0].desired_rect, 0, screen_height - 15, screen_width, screen_height);
235     SetWindowLongPtrA(windows[0].hwnd, GWLP_USERDATA, (LONG_PTR)&windows[0]);
236     testwindow_setpos(windows[0].hwnd);
237     do_events();
238 
239     /* create and register windows[1] */
240     windows[1].hwnd = CreateWindowExA(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
241         testwindow_class, testwindow_class, WS_POPUP|WS_VISIBLE, 0, 0, 0, 0,
242         NULL, NULL, NULL, NULL);
243     ok(windows[1].hwnd != NULL, "couldn't create window\n");
244     abd.hWnd = windows[1].hwnd;
245     ret = SHAppBarMessage(ABM_NEW, &abd);
246     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
247 
248     /* dock windows[1] to the bottom of the screen */
249     windows[1].registered = TRUE;
250     windows[1].to_be_deleted = FALSE;
251     windows[1].edge = ABE_BOTTOM;
252     SetRect(&windows[1].desired_rect, 0, screen_height - 10, screen_width, screen_height);
253     SetWindowLongPtrA(windows[1].hwnd, GWLP_USERDATA, (LONG_PTR)&windows[1]);
254     testwindow_setpos(windows[1].hwnd);
255 
256     /* the windows are adjusted to they don't overlap */
257     do_events_until(no_appbars_intersect);
258     test_window_rects(0, 1);
259 
260     /* make windows[0] larger, forcing windows[1] to move out of its way */
261     windows[0].desired_rect.top = screen_height - 20;
262     testwindow_setpos(windows[0].hwnd);
263     do_events_until(no_appbars_intersect);
264     test_window_rects(0, 1);
265 
266     /* create and register windows[2] */
267     windows[2].hwnd = CreateWindowExA(WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
268         testwindow_class, testwindow_class, WS_POPUP|WS_VISIBLE, 0, 0, 0, 0,
269         NULL, NULL, NULL, NULL);
270     ok(windows[2].hwnd != NULL, "couldn't create window\n");
271     do_events();
272 
273     abd.hWnd = windows[2].hwnd;
274     ret = SHAppBarMessage(ABM_NEW, &abd);
275     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
276 
277     /* dock windows[2] to the bottom of the screen */
278     windows[2].registered = TRUE;
279     windows[2].to_be_deleted = FALSE;
280     windows[2].edge = ABE_BOTTOM;
281     SetRect(&windows[2].desired_rect, 0, screen_height - 10, screen_width, screen_height);
282     SetWindowLongPtrA(windows[2].hwnd, GWLP_USERDATA, (LONG_PTR)&windows[2]);
283     testwindow_setpos(windows[2].hwnd);
284 
285     do_events_until(no_appbars_intersect);
286     test_window_rects(0, 1);
287     test_window_rects(0, 2);
288     test_window_rects(1, 2);
289 
290     /* move windows[2] to the right side of the screen */
291     windows[2].edge = ABE_RIGHT;
292     SetRect(&windows[2].desired_rect, screen_width - 15, 0, screen_width, screen_height);
293     testwindow_setpos(windows[2].hwnd);
294 
295     do_events_until(no_appbars_intersect);
296     test_window_rects(0, 1);
297     test_window_rects(0, 2);
298     test_window_rects(1, 2);
299 
300     /* move windows[1] to the top of the screen */
301     windows[1].edge = ABE_TOP;
302     SetRect(&windows[1].desired_rect, 0, 0, screen_width, 15);
303     testwindow_setpos(windows[1].hwnd);
304 
305     do_events_until(no_appbars_intersect);
306     test_window_rects(0, 1);
307     test_window_rects(0, 2);
308     test_window_rects(1, 2);
309 
310     /* move windows[1] back to the bottom of the screen */
311     windows[1].edge = ABE_BOTTOM;
312     SetRect(&windows[1].desired_rect, 0, screen_height - 10, screen_width, screen_height);
313     testwindow_setpos(windows[1].hwnd);
314 
315     do_events_until(no_appbars_intersect);
316     test_window_rects(0, 1);
317     test_window_rects(0, 2);
318     test_window_rects(1, 2);
319 
320     /* removing windows[0] will cause windows[1] to move down into its space */
321     expected_bottom = max(windows[0].allocated_rect.bottom, windows[1].allocated_rect.bottom);
322     org_bottom1 = windows[1].allocated_rect.bottom;
323     ok(windows[0].allocated_rect.bottom > windows[1].allocated_rect.bottom,
324         "Expected windows[0] to be lower than windows[1]\n");
325 
326     abd.hWnd = windows[0].hwnd;
327     windows[0].to_be_deleted = TRUE;
328     ret = SHAppBarMessage(ABM_REMOVE, &abd);
329     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
330     windows[0].registered = FALSE;
331     DestroyWindow(windows[0].hwnd);
332 
333     do_events_until(got_expected_bottom);
334 
335     if (windows[1].allocated_rect.bottom == org_bottom1)
336         win_skip("Some broken Vista boxes don't move the higher appbar down\n");
337     else
338         ok(windows[1].allocated_rect.bottom == expected_bottom,
339             "windows[1]'s bottom is %i, expected %i\n",
340             windows[1].allocated_rect.bottom, expected_bottom);
341 
342     test_window_rects(1, 2);
343 
344     /* remove the other windows */
345     abd.hWnd = windows[1].hwnd;
346     windows[1].to_be_deleted = TRUE;
347     ret = SHAppBarMessage(ABM_REMOVE, &abd);
348     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
349     windows[1].registered = FALSE;
350     DestroyWindow(windows[1].hwnd);
351 
352     abd.hWnd = windows[2].hwnd;
353     windows[2].to_be_deleted = TRUE;
354     ret = SHAppBarMessage(ABM_REMOVE, &abd);
355     ok(ret == TRUE, "SHAppBarMessage returned %i\n", ret);
356     windows[2].registered = FALSE;
357     DestroyWindow(windows[2].hwnd);
358 }
359 
360 static void test_appbarget(void)
361 {
362     APPBARDATA abd;
363     HWND hwnd, foregnd, unset_hwnd;
364     UINT_PTR ret;
365 
366     memset(&abd, 0xcc, sizeof(abd));
367     memset(&unset_hwnd, 0xcc, sizeof(unset_hwnd));
368     abd.cbSize = sizeof(abd);
369     abd.uEdge = ABE_BOTTOM;
370 
371     hwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);
372     ok(hwnd == NULL || IsWindow(hwnd), "ret %p which is not a window\n", hwnd);
373     ok(abd.hWnd == unset_hwnd, "hWnd overwritten %p\n",abd.hWnd);
374 
375     if (!pMonitorFromWindow)
376     {
377         win_skip("MonitorFromWindow is not available\n");
378     }
379     else
380     {
381         /* Presumably one can pass a hwnd with ABM_GETAUTOHIDEBAR to specify a monitor.
382            Pass the foreground window and check */
383         foregnd = GetForegroundWindow();
384         if(foregnd)
385         {
386             abd.hWnd = foregnd;
387             hwnd = (HWND)SHAppBarMessage(ABM_GETAUTOHIDEBAR, &abd);
388             ok(hwnd == NULL || IsWindow(hwnd), "ret %p which is not a window\n", hwnd);
389             ok(abd.hWnd == foregnd, "hWnd overwritten\n");
390             if(hwnd)
391             {
392                 HMONITOR appbar_mon, foregnd_mon;
393                 appbar_mon = pMonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
394                 foregnd_mon = pMonitorFromWindow(foregnd, MONITOR_DEFAULTTONEAREST);
395                 ok(appbar_mon == foregnd_mon, "Windows on different monitors\n");
396             }
397         }
398     }
399 
400     memset(&abd, 0xcc, sizeof(abd));
401     abd.cbSize = sizeof(abd);
402     ret = SHAppBarMessage(ABM_GETTASKBARPOS, &abd);
403     if(ret)
404     {
405         ok(abd.hWnd == (HWND)0xcccccccc, "hWnd overwritten\n");
406         ok(abd.uEdge <= ABE_BOTTOM ||
407             broken(abd.uEdge == 0xcccccccc), /* Some Win95 and NT4 */
408             "uEdge not returned\n");
409         ok(abd.rc.left != 0xcccccccc, "rc not updated\n");
410     }
411 
412     return;
413 }
414 
415 static void test_GetCurrentProcessExplicitAppUserModelID(void)
416 {
417     WCHAR *appid;
418     HRESULT hr;
419 
420     if (!pGetCurrentProcessExplicitAppUserModelID)
421     {
422         win_skip("GetCurrentProcessExplicitAppUserModelID() is not supported.\n");
423         return;
424     }
425 
426     if (0) /* crashes on native */
427         hr = pGetCurrentProcessExplicitAppUserModelID(NULL);
428 
429     appid = (void*)0xdeadbeef;
430     hr = pGetCurrentProcessExplicitAppUserModelID(&appid);
431 todo_wine
432     ok(hr == E_FAIL, "got 0x%08x\n", hr);
433     ok(appid == NULL, "got %p\n", appid);
434 }
435 
436 START_TEST(appbar)
437 {
438     HMODULE huser32, hshell32;
439 
440     huser32 = GetModuleHandleA("user32.dll");
441     hshell32 = GetModuleHandleA("shell32.dll");
442     pMonitorFromWindow = (void*)GetProcAddress(huser32, "MonitorFromWindow");
443     pGetCurrentProcessExplicitAppUserModelID = (void*)GetProcAddress(hshell32, "GetCurrentProcessExplicitAppUserModelID");
444 
445     register_testwindow_class();
446 
447     test_setpos();
448     test_appbarget();
449     test_GetCurrentProcessExplicitAppUserModelID();
450 }
451