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