xref: /reactos/base/shell/explorer/taskswnd.cpp (revision cc439606)
1 /*
2  * ReactOS Explorer
3  *
4  * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 #include "precomp.h"
22 #include <commoncontrols.h>
23 
24 /* Set DUMP_TASKS to 1 to enable a dump of the tasks and task groups every
25    5 seconds */
26 #define DUMP_TASKS  0
27 #define DEBUG_SHELL_HOOK 0
28 
29 #define MAX_TASKS_COUNT (0x7FFF)
30 #define TASK_ITEM_ARRAY_ALLOC   64
31 
32 const WCHAR szTaskSwitchWndClass[] = L"MSTaskSwWClass";
33 const WCHAR szRunningApps[] = L"Running Applications";
34 
35 #if DEBUG_SHELL_HOOK
36 const struct {
37     INT msg;
38     LPCWSTR msg_name;
39 } hshell_msg [] = {
40         { HSHELL_WINDOWCREATED, L"HSHELL_WINDOWCREATED" },
41         { HSHELL_WINDOWDESTROYED, L"HSHELL_WINDOWDESTROYED" },
42         { HSHELL_ACTIVATESHELLWINDOW, L"HSHELL_ACTIVATESHELLWINDOW" },
43         { HSHELL_WINDOWACTIVATED, L"HSHELL_WINDOWACTIVATED" },
44         { HSHELL_GETMINRECT, L"HSHELL_GETMINRECT" },
45         { HSHELL_REDRAW, L"HSHELL_REDRAW" },
46         { HSHELL_TASKMAN, L"HSHELL_TASKMAN" },
47         { HSHELL_LANGUAGE, L"HSHELL_LANGUAGE" },
48         { HSHELL_SYSMENU, L"HSHELL_SYSMENU" },
49         { HSHELL_ENDTASK, L"HSHELL_ENDTASK" },
50         { HSHELL_ACCESSIBILITYSTATE, L"HSHELL_ACCESSIBILITYSTATE" },
51         { HSHELL_APPCOMMAND, L"HSHELL_APPCOMMAND" },
52         { HSHELL_WINDOWREPLACED, L"HSHELL_WINDOWREPLACED" },
53         { HSHELL_WINDOWREPLACING, L"HSHELL_WINDOWREPLACING" },
54         { HSHELL_RUDEAPPACTIVATED, L"HSHELL_RUDEAPPACTIVATED" },
55 };
56 #endif
57 
58 typedef struct _TASK_GROUP
59 {
60     /* We have to use a linked list instead of an array so we don't have to
61        update all pointers to groups in the task item array when removing
62        groups. */
63     struct _TASK_GROUP *Next;
64 
65     DWORD dwTaskCount;
66     DWORD dwProcessId;
67     INT Index;
68     union
69     {
70         DWORD dwFlags;
71         struct
72         {
73 
74             DWORD IsCollapsed : 1;
75         };
76     };
77 } TASK_GROUP, *PTASK_GROUP;
78 
79 typedef struct _TASK_ITEM
80 {
81     HWND hWnd;
82     PTASK_GROUP Group;
83     INT Index;
84     INT IconIndex;
85 
86     union
87     {
88         DWORD dwFlags;
89         struct
90         {
91 
92             /* IsFlashing is TRUE when the task bar item should be flashing. */
93             DWORD IsFlashing : 1;
94 
95             /* RenderFlashed is only TRUE if the task bar item should be
96                drawn with a flash. */
97             DWORD RenderFlashed : 1;
98         };
99     };
100 } TASK_ITEM, *PTASK_ITEM;
101 
102 
103 class CHardErrorThread
104 {
105     DWORD m_ThreadId;
106     HANDLE m_hThread;
107     LONG m_bThreadRunning;
108     DWORD m_Status;
109     DWORD m_dwType;
110     CStringW m_Title;
111     CStringW m_Text;
112 public:
113 
114     CHardErrorThread():
115         m_ThreadId(0),
116         m_hThread(NULL),
117         m_bThreadRunning(FALSE),
118         m_Status(NULL),
119         m_dwType(NULL)
120     {
121     }
122 
123     ~CHardErrorThread()
124     {
125         if (m_bThreadRunning)
126         {
127             /* Try to unstuck Show */
128             PostThreadMessage(m_ThreadId, WM_QUIT, 0, 0);
129             DWORD ret = WaitForSingleObject(m_hThread, 3*1000);
130             if (ret == WAIT_TIMEOUT)
131                 TerminateThread(m_hThread, 0);
132             CloseHandle(m_hThread);
133         }
134     }
135 
136     HRESULT ThreadProc()
137     {
138         HRESULT hr;
139         CComPtr<IUserNotification> pnotification;
140 
141         hr = OleInitialize(NULL);
142         if (FAILED_UNEXPECTEDLY(hr))
143             return hr;
144 
145         hr = CoCreateInstance(CLSID_UserNotification,
146                               NULL,
147                               CLSCTX_INPROC_SERVER,
148                               IID_PPV_ARG(IUserNotification, &pnotification));
149         if (FAILED_UNEXPECTEDLY(hr))
150             return hr;
151 
152         hr = pnotification->SetBalloonInfo(m_Title, m_Text, NIIF_WARNING);
153         if (FAILED_UNEXPECTEDLY(hr))
154             return hr;
155 
156         hr = pnotification->SetIconInfo(NULL, NULL);
157         if (FAILED_UNEXPECTEDLY(hr))
158             return hr;
159 
160         /* Show will block until the balloon closes */
161         hr = pnotification->Show(NULL, 0);
162         if (FAILED_UNEXPECTEDLY(hr))
163             return hr;
164 
165         return S_OK;
166     }
167 
168     static DWORD CALLBACK s_HardErrorThreadProc(IN OUT LPVOID lpParameter)
169     {
170         CHardErrorThread* pThis = reinterpret_cast<CHardErrorThread*>(lpParameter);
171         pThis->ThreadProc();
172         CloseHandle(pThis->m_hThread);
173         OleUninitialize();
174         InterlockedExchange(&pThis->m_bThreadRunning, FALSE);
175         return 0;
176     }
177 
178     void StartThread(PBALLOON_HARD_ERROR_DATA pData)
179     {
180         BOOL bIsRunning = InterlockedExchange(&m_bThreadRunning, TRUE);
181 
182         /* Ignore the new message if we are already showing one */
183         if (bIsRunning)
184             return;
185 
186         m_Status = pData->Status;
187         m_dwType = pData->dwType;
188         m_Title = (PWCHAR)((ULONG_PTR)pData + pData->TitleOffset);
189         m_Text = (PWCHAR)((ULONG_PTR)pData + pData->MessageOffset);
190         m_hThread = CreateThread(NULL, 0, s_HardErrorThreadProc, this, 0, &m_ThreadId);
191         if (!m_hThread)
192         {
193             m_bThreadRunning = FALSE;
194             CloseHandle(m_hThread);
195         }
196     }
197 };
198 
199 class CTaskToolbar :
200     public CWindowImplBaseT< CToolbar<TASK_ITEM>, CControlWinTraits >
201 {
202 public:
203     INT UpdateTbButtonSpacing(IN BOOL bHorizontal, IN BOOL bThemed, IN UINT uiRows = 0, IN UINT uiBtnsPerLine = 0)
204     {
205         TBMETRICS tbm;
206 
207         tbm.cbSize = sizeof(tbm);
208         tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING;
209 
210         tbm.cxBarPad = tbm.cyBarPad = 0;
211 
212         if (bThemed)
213         {
214             tbm.cxButtonSpacing = 0;
215             tbm.cyButtonSpacing = 0;
216         }
217         else
218         {
219             if (bHorizontal || uiBtnsPerLine > 1)
220                 tbm.cxButtonSpacing = (3 * GetSystemMetrics(SM_CXEDGE) / 2);
221             else
222                 tbm.cxButtonSpacing = 0;
223 
224             if (!bHorizontal || uiRows > 1)
225                 tbm.cyButtonSpacing = (3 * GetSystemMetrics(SM_CYEDGE) / 2);
226             else
227                 tbm.cyButtonSpacing = 0;
228         }
229 
230         SetMetrics(&tbm);
231 
232         return tbm.cxButtonSpacing;
233     }
234 
235     VOID BeginUpdate()
236     {
237         SetRedraw(FALSE);
238     }
239 
240     VOID EndUpdate()
241     {
242         SendMessageW(WM_SETREDRAW, TRUE);
243         InvalidateRect(NULL, TRUE);
244     }
245 
246     BOOL SetButtonCommandId(IN INT iButtonIndex, IN INT iCommandId)
247     {
248         TBBUTTONINFO tbbi;
249 
250         tbbi.cbSize = sizeof(tbbi);
251         tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
252         tbbi.idCommand = iCommandId;
253 
254         return SetButtonInfo(iButtonIndex, &tbbi) != 0;
255     }
256 
257     LRESULT OnNcHitTestToolbar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
258     {
259         POINT pt;
260 
261         /* See if the mouse is on a button */
262         pt.x = GET_X_LPARAM(lParam);
263         pt.y = GET_Y_LPARAM(lParam);
264         ScreenToClient(&pt);
265 
266         INT index = HitTest(&pt);
267         if (index < 0)
268         {
269             /* Make the control appear to be transparent outside of any buttons */
270             return HTTRANSPARENT;
271         }
272 
273         bHandled = FALSE;
274         return 0;
275     }
276 
277 public:
278     BEGIN_MSG_MAP(CNotifyToolbar)
279         MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTestToolbar)
280     END_MSG_MAP()
281 
282     BOOL Initialize(HWND hWndParent)
283     {
284         DWORD styles = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
285             TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_LIST | TBSTYLE_TRANSPARENT |
286             CCS_TOP | CCS_NORESIZE | CCS_NODIVIDER;
287 
288         return SubclassWindow(CToolbar::Create(hWndParent, styles));
289     }
290 };
291 
292 class CTaskSwitchWnd :
293     public CComCoClass<CTaskSwitchWnd>,
294     public CComObjectRootEx<CComMultiThreadModelNoCS>,
295     public CWindowImpl < CTaskSwitchWnd, CWindow, CControlWinTraits >,
296     public IOleWindow
297 {
298     CTaskToolbar m_TaskBar;
299 
300     CComPtr<ITrayWindow> m_Tray;
301 
302     UINT m_ShellHookMsg;
303 
304     WORD m_TaskItemCount;
305     WORD m_AllocatedTaskItems;
306 
307     PTASK_GROUP m_TaskGroups;
308     PTASK_ITEM m_TaskItems;
309     PTASK_ITEM m_ActiveTaskItem;
310 
311     HTHEME m_Theme;
312     UINT m_ButtonsPerLine;
313     WORD m_ButtonCount;
314 
315     HIMAGELIST m_ImageList;
316 
317     BOOL m_IsGroupingEnabled;
318     BOOL m_IsDestroying;
319 
320     SIZE m_ButtonSize;
321 
322     UINT m_uHardErrorMsg;
323     CHardErrorThread m_HardErrorThread;
324 
325 public:
326     CTaskSwitchWnd() :
327         m_ShellHookMsg(NULL),
328         m_TaskItemCount(0),
329         m_AllocatedTaskItems(0),
330         m_TaskGroups(NULL),
331         m_TaskItems(NULL),
332         m_ActiveTaskItem(NULL),
333         m_Theme(NULL),
334         m_ButtonsPerLine(0),
335         m_ButtonCount(0),
336         m_ImageList(NULL),
337         m_IsGroupingEnabled(FALSE),
338         m_IsDestroying(FALSE)
339     {
340         ZeroMemory(&m_ButtonSize, sizeof(m_ButtonSize));
341         m_uHardErrorMsg = RegisterWindowMessageW(L"HardError");
342     }
343     virtual ~CTaskSwitchWnd() { }
344 
345     INT GetWndTextFromTaskItem(IN PTASK_ITEM TaskItem, LPWSTR szBuf, DWORD cchBuf)
346     {
347         /* Get the window text without sending a message so we don't hang if an
348            application isn't responding! */
349         return InternalGetWindowText(TaskItem->hWnd, szBuf, cchBuf);
350     }
351 
352 
353 #if DUMP_TASKS != 0
354     VOID DumpTasks()
355     {
356         PTASK_GROUP CurrentGroup;
357         PTASK_ITEM CurrentTaskItem, LastTaskItem;
358 
359         TRACE("Tasks dump:\n");
360         if (m_IsGroupingEnabled)
361         {
362             CurrentGroup = m_TaskGroups;
363             while (CurrentGroup != NULL)
364             {
365                 TRACE("- Group PID: 0x%p Tasks: %d Index: %d\n", CurrentGroup->dwProcessId, CurrentGroup->dwTaskCount, CurrentGroup->Index);
366 
367                 CurrentTaskItem = m_TaskItems;
368                 LastTaskItem = CurrentTaskItem + m_TaskItemCount;
369                 while (CurrentTaskItem != LastTaskItem)
370                 {
371                     if (CurrentTaskItem->Group == CurrentGroup)
372                     {
373                         TRACE("  + Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
374                     }
375                     CurrentTaskItem++;
376                 }
377 
378                 CurrentGroup = CurrentGroup->Next;
379             }
380 
381             CurrentTaskItem = m_TaskItems;
382             LastTaskItem = CurrentTaskItem + m_TaskItemCount;
383             while (CurrentTaskItem != LastTaskItem)
384             {
385                 if (CurrentTaskItem->Group == NULL)
386                 {
387                     TRACE("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
388                 }
389                 CurrentTaskItem++;
390             }
391         }
392         else
393         {
394             CurrentTaskItem = m_TaskItems;
395             LastTaskItem = CurrentTaskItem + m_TaskItemCount;
396             while (CurrentTaskItem != LastTaskItem)
397             {
398                 TRACE("- Task hwnd: 0x%p Index: %d\n", CurrentTaskItem->hWnd, CurrentTaskItem->Index);
399                 CurrentTaskItem++;
400             }
401         }
402     }
403 #endif
404 
405     VOID UpdateIndexesAfter(IN INT iIndex, BOOL bInserted)
406     {
407         PTASK_GROUP CurrentGroup;
408         PTASK_ITEM CurrentTaskItem, LastTaskItem;
409         INT NewIndex;
410 
411         int offset = bInserted ? +1 : -1;
412 
413         if (m_IsGroupingEnabled)
414         {
415             /* Update all affected groups */
416             CurrentGroup = m_TaskGroups;
417             while (CurrentGroup != NULL)
418             {
419                 if (CurrentGroup->IsCollapsed &&
420                     CurrentGroup->Index >= iIndex)
421                 {
422                     /* Update the toolbar buttons */
423                     NewIndex = CurrentGroup->Index + offset;
424                     if (m_TaskBar.SetButtonCommandId(CurrentGroup->Index + offset, NewIndex))
425                     {
426                         CurrentGroup->Index = NewIndex;
427                     }
428                     else
429                         CurrentGroup->Index = -1;
430                 }
431 
432                 CurrentGroup = CurrentGroup->Next;
433             }
434         }
435 
436         /* Update all affected task items */
437         CurrentTaskItem = m_TaskItems;
438         LastTaskItem = CurrentTaskItem + m_TaskItemCount;
439         while (CurrentTaskItem != LastTaskItem)
440         {
441             CurrentGroup = CurrentTaskItem->Group;
442             if (CurrentGroup != NULL)
443             {
444                 if (!CurrentGroup->IsCollapsed &&
445                     CurrentTaskItem->Index >= iIndex)
446                 {
447                     goto UpdateTaskItemBtn;
448                 }
449             }
450             else if (CurrentTaskItem->Index >= iIndex)
451             {
452             UpdateTaskItemBtn:
453                 /* Update the toolbar buttons */
454                 NewIndex = CurrentTaskItem->Index + offset;
455                 if (m_TaskBar.SetButtonCommandId(CurrentTaskItem->Index + offset, NewIndex))
456                 {
457                     CurrentTaskItem->Index = NewIndex;
458                 }
459                 else
460                     CurrentTaskItem->Index = -1;
461             }
462 
463             CurrentTaskItem++;
464         }
465     }
466 
467 
468     INT UpdateTaskGroupButton(IN PTASK_GROUP TaskGroup)
469     {
470         ASSERT(TaskGroup->Index >= 0);
471 
472         /* FIXME: Implement */
473 
474         return TaskGroup->Index;
475     }
476 
477     VOID ExpandTaskGroup(IN PTASK_GROUP TaskGroup)
478     {
479         ASSERT(TaskGroup->dwTaskCount > 0);
480         ASSERT(TaskGroup->IsCollapsed);
481         ASSERT(TaskGroup->Index >= 0);
482 
483         /* FIXME: Implement */
484     }
485 
486     HICON GetWndIcon(HWND hwnd)
487     {
488         HICON hIcon = 0;
489 
490         SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR) &hIcon);
491         if (hIcon)
492             return hIcon;
493 
494         SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR) &hIcon);
495         if (hIcon)
496             return hIcon;
497 
498         SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR) &hIcon);
499         if (hIcon)
500             return hIcon;
501 
502         hIcon = (HICON) GetClassLongPtr(hwnd, GCL_HICONSM);
503         if (hIcon)
504             return hIcon;
505 
506         hIcon = (HICON) GetClassLongPtr(hwnd, GCL_HICON);
507 
508         return hIcon;
509     }
510 
511     INT UpdateTaskItemButton(IN PTASK_ITEM TaskItem)
512     {
513         TBBUTTONINFO tbbi = { 0 };
514         HICON icon;
515         WCHAR windowText[255];
516 
517         ASSERT(TaskItem->Index >= 0);
518 
519         tbbi.cbSize = sizeof(tbbi);
520         tbbi.dwMask = TBIF_BYINDEX | TBIF_STATE | TBIF_TEXT | TBIF_IMAGE;
521         tbbi.fsState = TBSTATE_ENABLED;
522         if (m_ActiveTaskItem == TaskItem)
523             tbbi.fsState |= TBSTATE_CHECKED;
524 
525         if (TaskItem->RenderFlashed)
526             tbbi.fsState |= TBSTATE_MARKED;
527 
528         /* Check if we're updating a button that is the last one in the
529            line. If so, we need to set the TBSTATE_WRAP flag! */
530         if (!m_Tray->IsHorizontal() || (m_ButtonsPerLine != 0 &&
531             (TaskItem->Index + 1) % m_ButtonsPerLine == 0))
532         {
533             tbbi.fsState |= TBSTATE_WRAP;
534         }
535 
536         if (GetWndTextFromTaskItem(TaskItem, windowText, _countof(windowText)) > 0)
537         {
538             tbbi.pszText = windowText;
539         }
540 
541         icon = GetWndIcon(TaskItem->hWnd);
542         if (!icon)
543             icon = static_cast<HICON>(LoadImageW(NULL, MAKEINTRESOURCEW(OIC_SAMPLE), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE));
544         TaskItem->IconIndex = ImageList_ReplaceIcon(m_ImageList, TaskItem->IconIndex, icon);
545         tbbi.iImage = TaskItem->IconIndex;
546 
547         if (!m_TaskBar.SetButtonInfo(TaskItem->Index, &tbbi))
548         {
549             TaskItem->Index = -1;
550             return -1;
551         }
552 
553         TRACE("Updated button %d for hwnd 0x%p\n", TaskItem->Index, TaskItem->hWnd);
554         return TaskItem->Index;
555     }
556 
557     VOID RemoveIcon(IN PTASK_ITEM TaskItem)
558     {
559         TBBUTTONINFO tbbi;
560         PTASK_ITEM currentTaskItem, LastItem;
561 
562         if (TaskItem->IconIndex == -1)
563             return;
564 
565         tbbi.cbSize = sizeof(tbbi);
566         tbbi.dwMask = TBIF_IMAGE;
567 
568         currentTaskItem = m_TaskItems;
569         LastItem = currentTaskItem + m_TaskItemCount;
570         while (currentTaskItem != LastItem)
571         {
572             if (currentTaskItem->IconIndex > TaskItem->IconIndex)
573             {
574                 currentTaskItem->IconIndex--;
575                 tbbi.iImage = currentTaskItem->IconIndex;
576 
577                 m_TaskBar.SetButtonInfo(currentTaskItem->Index, &tbbi);
578             }
579             currentTaskItem++;
580         }
581 
582         ImageList_Remove(m_ImageList, TaskItem->IconIndex);
583     }
584 
585     PTASK_ITEM FindLastTaskItemOfGroup(
586         IN PTASK_GROUP TaskGroup  OPTIONAL,
587         IN PTASK_ITEM NewTaskItem  OPTIONAL)
588     {
589         PTASK_ITEM TaskItem, LastTaskItem, FoundTaskItem = NULL;
590         DWORD dwTaskCount;
591 
592         ASSERT(m_IsGroupingEnabled);
593 
594         TaskItem = m_TaskItems;
595         LastTaskItem = TaskItem + m_TaskItemCount;
596 
597         dwTaskCount = (TaskGroup != NULL ? TaskGroup->dwTaskCount : MAX_TASKS_COUNT);
598 
599         ASSERT(dwTaskCount > 0);
600 
601         while (TaskItem != LastTaskItem)
602         {
603             if (TaskItem->Group == TaskGroup)
604             {
605                 if ((NewTaskItem != NULL && TaskItem != NewTaskItem) || NewTaskItem == NULL)
606                 {
607                     FoundTaskItem = TaskItem;
608                 }
609 
610                 if (--dwTaskCount == 0)
611                 {
612                     /* We found the last task item in the group! */
613                     break;
614                 }
615             }
616 
617             TaskItem++;
618         }
619 
620         return FoundTaskItem;
621     }
622 
623     INT CalculateTaskItemNewButtonIndex(IN PTASK_ITEM TaskItem)
624     {
625         PTASK_GROUP TaskGroup;
626         PTASK_ITEM LastTaskItem;
627 
628         /* NOTE: This routine assumes that the group is *not* collapsed! */
629 
630         TaskGroup = TaskItem->Group;
631         if (m_IsGroupingEnabled)
632         {
633             if (TaskGroup != NULL)
634             {
635                 ASSERT(TaskGroup->Index < 0);
636                 ASSERT(!TaskGroup->IsCollapsed);
637 
638                 if (TaskGroup->dwTaskCount > 1)
639                 {
640                     LastTaskItem = FindLastTaskItemOfGroup(TaskGroup, TaskItem);
641                     if (LastTaskItem != NULL)
642                     {
643                         /* Since the group is expanded the task items must have an index */
644                         ASSERT(LastTaskItem->Index >= 0);
645 
646                         return LastTaskItem->Index + 1;
647                     }
648                 }
649             }
650             else
651             {
652                 /* Find the last NULL group button. NULL groups are added at the end of the
653                    task item list when grouping is enabled */
654                 LastTaskItem = FindLastTaskItemOfGroup(NULL, TaskItem);
655                 if (LastTaskItem != NULL)
656                 {
657                     ASSERT(LastTaskItem->Index >= 0);
658 
659                     return LastTaskItem->Index + 1;
660                 }
661             }
662         }
663 
664         return m_ButtonCount;
665     }
666 
667     INT AddTaskItemButton(IN OUT PTASK_ITEM TaskItem)
668     {
669         WCHAR windowText[255];
670         TBBUTTON tbBtn = { 0 };
671         INT iIndex;
672         HICON icon;
673 
674         if (TaskItem->Index >= 0)
675         {
676             return UpdateTaskItemButton(TaskItem);
677         }
678 
679         if (TaskItem->Group != NULL &&
680             TaskItem->Group->IsCollapsed)
681         {
682             /* The task group is collapsed, we only need to update the group button */
683             return UpdateTaskGroupButton(TaskItem->Group);
684         }
685 
686         icon = GetWndIcon(TaskItem->hWnd);
687         if (!icon)
688             icon = static_cast<HICON>(LoadImageW(NULL, MAKEINTRESOURCEW(OIC_SAMPLE), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE));
689         TaskItem->IconIndex = ImageList_ReplaceIcon(m_ImageList, -1, icon);
690 
691         tbBtn.iBitmap = TaskItem->IconIndex;
692         tbBtn.fsState = TBSTATE_ENABLED | TBSTATE_ELLIPSES;
693         tbBtn.fsStyle = BTNS_CHECK | BTNS_NOPREFIX | BTNS_SHOWTEXT;
694         tbBtn.dwData = TaskItem->Index;
695 
696         if (GetWndTextFromTaskItem(TaskItem, windowText, _countof(windowText)) > 0)
697         {
698             tbBtn.iString = (DWORD_PTR) windowText;
699         }
700 
701         /* Find out where to insert the new button */
702         iIndex = CalculateTaskItemNewButtonIndex(TaskItem);
703         ASSERT(iIndex >= 0);
704         tbBtn.idCommand = iIndex;
705 
706         m_TaskBar.BeginUpdate();
707 
708         if (m_TaskBar.InsertButton(iIndex, &tbBtn))
709         {
710             UpdateIndexesAfter(iIndex, TRUE);
711 
712             TRACE("Added button %d for hwnd 0x%p\n", iIndex, TaskItem->hWnd);
713 
714             TaskItem->Index = iIndex;
715             m_ButtonCount++;
716 
717             /* Update button sizes and fix the button wrapping */
718             UpdateButtonsSize(TRUE);
719             return iIndex;
720         }
721 
722         m_TaskBar.EndUpdate();
723 
724         return -1;
725     }
726 
727     BOOL DeleteTaskItemButton(IN OUT PTASK_ITEM TaskItem)
728     {
729         PTASK_GROUP TaskGroup;
730         INT iIndex;
731 
732         TaskGroup = TaskItem->Group;
733 
734         if (TaskItem->Index >= 0)
735         {
736             if ((TaskGroup != NULL && !TaskGroup->IsCollapsed) ||
737                 TaskGroup == NULL)
738             {
739                 m_TaskBar.BeginUpdate();
740 
741                 RemoveIcon(TaskItem);
742                 iIndex = TaskItem->Index;
743                 if (m_TaskBar.DeleteButton(iIndex))
744                 {
745                     TaskItem->Index = -1;
746                     m_ButtonCount--;
747 
748                     UpdateIndexesAfter(iIndex, FALSE);
749 
750                     /* Update button sizes and fix the button wrapping */
751                     UpdateButtonsSize(TRUE);
752                     return TRUE;
753                 }
754 
755                 m_TaskBar.EndUpdate();
756             }
757         }
758 
759         return FALSE;
760     }
761 
762     PTASK_GROUP AddToTaskGroup(IN HWND hWnd)
763     {
764         DWORD dwProcessId;
765         PTASK_GROUP TaskGroup, *PrevLink;
766 
767         if (!GetWindowThreadProcessId(hWnd,
768             &dwProcessId))
769         {
770             TRACE("Cannot get process id of hwnd 0x%p\n", hWnd);
771             return NULL;
772         }
773 
774         /* Try to find an existing task group */
775         TaskGroup = m_TaskGroups;
776         PrevLink = &m_TaskGroups;
777         while (TaskGroup != NULL)
778         {
779             if (TaskGroup->dwProcessId == dwProcessId)
780             {
781                 TaskGroup->dwTaskCount++;
782                 return TaskGroup;
783             }
784 
785             PrevLink = &TaskGroup->Next;
786             TaskGroup = TaskGroup->Next;
787         }
788 
789         /* Allocate a new task group */
790         TaskGroup = (PTASK_GROUP) HeapAlloc(hProcessHeap,
791             HEAP_ZERO_MEMORY,
792             sizeof(*TaskGroup));
793         if (TaskGroup != NULL)
794         {
795             TaskGroup->dwTaskCount = 1;
796             TaskGroup->dwProcessId = dwProcessId;
797             TaskGroup->Index = -1;
798 
799             /* Add the task group to the list */
800             *PrevLink = TaskGroup;
801         }
802 
803         return TaskGroup;
804     }
805 
806     VOID RemoveTaskFromTaskGroup(IN OUT PTASK_ITEM TaskItem)
807     {
808         PTASK_GROUP TaskGroup, CurrentGroup, *PrevLink;
809 
810         TaskGroup = TaskItem->Group;
811         if (TaskGroup != NULL)
812         {
813             DWORD dwNewTaskCount = --TaskGroup->dwTaskCount;
814             if (dwNewTaskCount == 0)
815             {
816                 /* Find the previous pointer in the chain */
817                 CurrentGroup = m_TaskGroups;
818                 PrevLink = &m_TaskGroups;
819                 while (CurrentGroup != TaskGroup)
820                 {
821                     PrevLink = &CurrentGroup->Next;
822                     CurrentGroup = CurrentGroup->Next;
823                 }
824 
825                 /* Remove the group from the list */
826                 ASSERT(TaskGroup == CurrentGroup);
827                 *PrevLink = TaskGroup->Next;
828 
829                 /* Free the task group */
830                 HeapFree(hProcessHeap,
831                     0,
832                     TaskGroup);
833             }
834             else if (TaskGroup->IsCollapsed &&
835                 TaskGroup->Index >= 0)
836             {
837                 if (dwNewTaskCount > 1)
838                 {
839                     /* FIXME: Check if we should expand the group */
840                     /* Update the task group button */
841                     UpdateTaskGroupButton(TaskGroup);
842                 }
843                 else
844                 {
845                     /* Expand the group of one task button to a task button */
846                     ExpandTaskGroup(TaskGroup);
847                 }
848             }
849         }
850     }
851 
852     PTASK_ITEM FindTaskItem(IN HWND hWnd)
853     {
854         PTASK_ITEM TaskItem, LastItem;
855 
856         TaskItem = m_TaskItems;
857         LastItem = TaskItem + m_TaskItemCount;
858         while (TaskItem != LastItem)
859         {
860             if (TaskItem->hWnd == hWnd)
861                 return TaskItem;
862 
863             TaskItem++;
864         }
865 
866         return NULL;
867     }
868 
869     PTASK_ITEM FindOtherTaskItem(IN HWND hWnd)
870     {
871         PTASK_ITEM LastItem, TaskItem;
872         PTASK_GROUP TaskGroup;
873         DWORD dwProcessId;
874 
875         if (!GetWindowThreadProcessId(hWnd, &dwProcessId))
876         {
877             return NULL;
878         }
879 
880         /* Try to find another task that belongs to the same
881            process as the given window */
882         TaskItem = m_TaskItems;
883         LastItem = TaskItem + m_TaskItemCount;
884         while (TaskItem != LastItem)
885         {
886             TaskGroup = TaskItem->Group;
887             if (TaskGroup != NULL)
888             {
889                 if (TaskGroup->dwProcessId == dwProcessId)
890                     return TaskItem;
891             }
892             else
893             {
894                 DWORD dwProcessIdTask;
895 
896                 if (GetWindowThreadProcessId(TaskItem->hWnd,
897                     &dwProcessIdTask) &&
898                     dwProcessIdTask == dwProcessId)
899                 {
900                     return TaskItem;
901                 }
902             }
903 
904             TaskItem++;
905         }
906 
907         return NULL;
908     }
909 
910     PTASK_ITEM AllocTaskItem()
911     {
912         if (m_TaskItemCount >= MAX_TASKS_COUNT)
913         {
914             /* We need the most significant bit in 16 bit command IDs to indicate whether it
915                is a task group or task item. WM_COMMAND limits command IDs to 16 bits! */
916             return NULL;
917         }
918 
919         ASSERT(m_AllocatedTaskItems >= m_TaskItemCount);
920 
921         if (m_TaskItemCount == 0)
922         {
923             m_TaskItems = (PTASK_ITEM) HeapAlloc(hProcessHeap,
924                 0,
925                 TASK_ITEM_ARRAY_ALLOC * sizeof(*m_TaskItems));
926             if (m_TaskItems != NULL)
927             {
928                 m_AllocatedTaskItems = TASK_ITEM_ARRAY_ALLOC;
929             }
930             else
931                 return NULL;
932         }
933         else if (m_TaskItemCount >= m_AllocatedTaskItems)
934         {
935             PTASK_ITEM NewArray;
936             SIZE_T NewArrayLength, ActiveTaskItemIndex;
937 
938             NewArrayLength = m_AllocatedTaskItems + TASK_ITEM_ARRAY_ALLOC;
939 
940             NewArray = (PTASK_ITEM) HeapReAlloc(hProcessHeap,
941                 0,
942                 m_TaskItems,
943                 NewArrayLength * sizeof(*m_TaskItems));
944             if (NewArray != NULL)
945             {
946                 if (m_ActiveTaskItem != NULL)
947                 {
948                     /* Fixup the ActiveTaskItem pointer */
949                     ActiveTaskItemIndex = m_ActiveTaskItem - m_TaskItems;
950                     m_ActiveTaskItem = NewArray + ActiveTaskItemIndex;
951                 }
952                 m_AllocatedTaskItems = (WORD) NewArrayLength;
953                 m_TaskItems = NewArray;
954             }
955             else
956                 return NULL;
957         }
958 
959         return m_TaskItems + m_TaskItemCount++;
960     }
961 
962     VOID FreeTaskItem(IN OUT PTASK_ITEM TaskItem)
963     {
964         WORD wIndex;
965 
966         if (TaskItem == m_ActiveTaskItem)
967             m_ActiveTaskItem = NULL;
968 
969         wIndex = (WORD) (TaskItem - m_TaskItems);
970         if (wIndex + 1 < m_TaskItemCount)
971         {
972             MoveMemory(TaskItem,
973                 TaskItem + 1,
974                 (m_TaskItemCount - wIndex - 1) * sizeof(*TaskItem));
975         }
976 
977         m_TaskItemCount--;
978     }
979 
980     VOID DeleteTaskItem(IN OUT PTASK_ITEM TaskItem)
981     {
982         if (!m_IsDestroying)
983         {
984             /* Delete the task button from the toolbar */
985             DeleteTaskItemButton(TaskItem);
986         }
987 
988         /* Remove the task from it's group */
989         RemoveTaskFromTaskGroup(TaskItem);
990 
991         /* Free the task item */
992         FreeTaskItem(TaskItem);
993     }
994 
995     VOID CheckActivateTaskItem(IN OUT PTASK_ITEM TaskItem)
996     {
997         PTASK_ITEM CurrentTaskItem;
998         PTASK_GROUP TaskGroup = NULL;
999 
1000         CurrentTaskItem = m_ActiveTaskItem;
1001 
1002         if (TaskItem != NULL)
1003             TaskGroup = TaskItem->Group;
1004 
1005         if (m_IsGroupingEnabled &&
1006             TaskGroup != NULL &&
1007             TaskGroup->IsCollapsed)
1008         {
1009             /* FIXME */
1010             return;
1011         }
1012 
1013         if (CurrentTaskItem != NULL)
1014         {
1015             PTASK_GROUP CurrentTaskGroup;
1016 
1017             if (CurrentTaskItem == TaskItem)
1018                 return;
1019 
1020             CurrentTaskGroup = CurrentTaskItem->Group;
1021 
1022             if (m_IsGroupingEnabled &&
1023                 CurrentTaskGroup != NULL &&
1024                 CurrentTaskGroup->IsCollapsed)
1025             {
1026                 if (CurrentTaskGroup == TaskGroup)
1027                     return;
1028 
1029                 /* FIXME */
1030             }
1031             else
1032             {
1033                 m_ActiveTaskItem = NULL;
1034                 if (CurrentTaskItem->Index >= 0)
1035                 {
1036                     UpdateTaskItemButton(CurrentTaskItem);
1037                 }
1038             }
1039         }
1040 
1041         m_ActiveTaskItem = TaskItem;
1042 
1043         if (TaskItem != NULL && TaskItem->Index >= 0)
1044         {
1045             UpdateTaskItemButton(TaskItem);
1046         }
1047         else if (TaskItem == NULL)
1048         {
1049             TRACE("Active TaskItem now NULL\n");
1050         }
1051     }
1052 
1053     PTASK_ITEM FindTaskItemByIndex(IN INT Index)
1054     {
1055         PTASK_ITEM TaskItem, LastItem;
1056 
1057         TaskItem = m_TaskItems;
1058         LastItem = TaskItem + m_TaskItemCount;
1059         while (TaskItem != LastItem)
1060         {
1061             if (TaskItem->Index == Index)
1062                 return TaskItem;
1063 
1064             TaskItem++;
1065         }
1066 
1067         return NULL;
1068     }
1069 
1070     PTASK_GROUP FindTaskGroupByIndex(IN INT Index)
1071     {
1072         PTASK_GROUP CurrentGroup;
1073 
1074         CurrentGroup = m_TaskGroups;
1075         while (CurrentGroup != NULL)
1076         {
1077             if (CurrentGroup->Index == Index)
1078                 break;
1079 
1080             CurrentGroup = CurrentGroup->Next;
1081         }
1082 
1083         return CurrentGroup;
1084     }
1085 
1086     BOOL AddTask(IN HWND hWnd)
1087     {
1088         PTASK_ITEM TaskItem;
1089 
1090         if (!::IsWindow(hWnd) || m_Tray->IsSpecialHWND(hWnd))
1091             return FALSE;
1092 
1093         TaskItem = FindTaskItem(hWnd);
1094         if (TaskItem == NULL)
1095         {
1096             TRACE("Add window 0x%p\n", hWnd);
1097             TaskItem = AllocTaskItem();
1098             if (TaskItem != NULL)
1099             {
1100                 ZeroMemory(TaskItem, sizeof(*TaskItem));
1101                 TaskItem->hWnd = hWnd;
1102                 TaskItem->Index = -1;
1103                 TaskItem->Group = AddToTaskGroup(hWnd);
1104 
1105                 if (!m_IsDestroying)
1106                 {
1107                     AddTaskItemButton(TaskItem);
1108                 }
1109             }
1110         }
1111 
1112         return TaskItem != NULL;
1113     }
1114 
1115     BOOL ActivateTaskItem(IN OUT PTASK_ITEM TaskItem  OPTIONAL)
1116     {
1117         if (TaskItem != NULL)
1118         {
1119             TRACE("Activate window 0x%p on button %d\n", TaskItem->hWnd, TaskItem->Index);
1120         }
1121 
1122         CheckActivateTaskItem(TaskItem);
1123 
1124         return FALSE;
1125     }
1126 
1127     BOOL ActivateTask(IN HWND hWnd)
1128     {
1129         PTASK_ITEM TaskItem;
1130 
1131         if (!hWnd)
1132         {
1133             return ActivateTaskItem(NULL);
1134         }
1135 
1136         TaskItem = FindTaskItem(hWnd);
1137         if (TaskItem == NULL)
1138         {
1139             TaskItem = FindOtherTaskItem(hWnd);
1140         }
1141 
1142         if (TaskItem == NULL)
1143         {
1144             WARN("Activate window 0x%p, could not find task\n", hWnd);
1145             RefreshWindowList();
1146         }
1147 
1148         return ActivateTaskItem(TaskItem);
1149     }
1150 
1151     BOOL DeleteTask(IN HWND hWnd)
1152     {
1153         PTASK_ITEM TaskItem;
1154 
1155         TaskItem = FindTaskItem(hWnd);
1156         if (TaskItem != NULL)
1157         {
1158             TRACE("Delete window 0x%p on button %d\n", hWnd, TaskItem->Index);
1159             DeleteTaskItem(TaskItem);
1160             return TRUE;
1161         }
1162         //else
1163         //TRACE("Failed to delete window 0x%p\n", hWnd);
1164 
1165         return FALSE;
1166     }
1167 
1168     VOID DeleteAllTasks()
1169     {
1170         PTASK_ITEM CurrentTask;
1171 
1172         if (m_TaskItemCount > 0)
1173         {
1174             CurrentTask = m_TaskItems + m_TaskItemCount;
1175             do
1176             {
1177                 DeleteTaskItem(--CurrentTask);
1178             } while (CurrentTask != m_TaskItems);
1179         }
1180     }
1181 
1182     VOID FlashTaskItem(IN OUT PTASK_ITEM TaskItem)
1183     {
1184         TaskItem->RenderFlashed = 1;
1185         UpdateTaskItemButton(TaskItem);
1186     }
1187 
1188     BOOL FlashTask(IN HWND hWnd)
1189     {
1190         PTASK_ITEM TaskItem;
1191 
1192         TaskItem = FindTaskItem(hWnd);
1193         if (TaskItem != NULL)
1194         {
1195             TRACE("Flashing window 0x%p on button %d\n", hWnd, TaskItem->Index);
1196             FlashTaskItem(TaskItem);
1197             return TRUE;
1198         }
1199 
1200         return FALSE;
1201     }
1202 
1203     VOID RedrawTaskItem(IN OUT PTASK_ITEM TaskItem)
1204     {
1205         PTASK_GROUP TaskGroup;
1206 
1207         TaskGroup = TaskItem->Group;
1208         if (m_IsGroupingEnabled && TaskGroup != NULL)
1209         {
1210             if (TaskGroup->IsCollapsed && TaskGroup->Index >= 0)
1211             {
1212                 UpdateTaskGroupButton(TaskGroup);
1213             }
1214             else if (TaskItem->Index >= 0)
1215             {
1216                 goto UpdateTaskItem;
1217             }
1218         }
1219         else if (TaskItem->Index >= 0)
1220         {
1221         UpdateTaskItem:
1222             TaskItem->RenderFlashed = 0;
1223             UpdateTaskItemButton(TaskItem);
1224         }
1225     }
1226 
1227 
1228     BOOL RedrawTask(IN HWND hWnd)
1229     {
1230         PTASK_ITEM TaskItem;
1231 
1232         TaskItem = FindTaskItem(hWnd);
1233         if (TaskItem != NULL)
1234         {
1235             RedrawTaskItem(TaskItem);
1236             return TRUE;
1237         }
1238 
1239         return FALSE;
1240     }
1241 
1242     VOID UpdateButtonsSize(IN BOOL bRedrawDisabled)
1243     {
1244         RECT rcClient;
1245         UINT uiRows, uiMax, uiMin, uiBtnsPerLine, ui;
1246         LONG NewBtnSize;
1247         BOOL Horizontal;
1248 
1249         /* Update the size of the image list if needed */
1250         int cx, cy;
1251         ImageList_GetIconSize(m_ImageList, &cx, &cy);
1252         if (cx != GetSystemMetrics(SM_CXSMICON) || cy != GetSystemMetrics(SM_CYSMICON))
1253         {
1254             ImageList_SetIconSize(m_ImageList, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
1255 
1256             /* SetIconSize removes all icons so we have to reinsert them */
1257             PTASK_ITEM TaskItem = m_TaskItems;
1258             PTASK_ITEM LastTaskItem = m_TaskItems + m_TaskItemCount;
1259             while (TaskItem != LastTaskItem)
1260             {
1261                 TaskItem->IconIndex = -1;
1262                 UpdateTaskItemButton(TaskItem);
1263 
1264                 TaskItem++;
1265             }
1266             m_TaskBar.SetImageList(m_ImageList);
1267         }
1268 
1269         if (GetClientRect(&rcClient) && !IsRectEmpty(&rcClient))
1270         {
1271             if (m_ButtonCount > 0)
1272             {
1273                 Horizontal = m_Tray->IsHorizontal();
1274 
1275                 if (Horizontal)
1276                 {
1277                     TBMETRICS tbm = { 0 };
1278                     tbm.cbSize = sizeof(tbm);
1279                     tbm.dwMask = TBMF_BUTTONSPACING;
1280                     m_TaskBar.GetMetrics(&tbm);
1281 
1282                     if (m_ButtonSize.cy + tbm.cyButtonSpacing != 0)
1283                         uiRows = (rcClient.bottom + tbm.cyButtonSpacing) / (m_ButtonSize.cy + tbm.cyButtonSpacing);
1284                     else
1285                         uiRows = 1;
1286 
1287                     if (uiRows == 0)
1288                         uiRows = 1;
1289 
1290                     uiBtnsPerLine = (m_ButtonCount + uiRows - 1) / uiRows;
1291                 }
1292                 else
1293                 {
1294                     uiBtnsPerLine = 1;
1295                     uiRows = m_ButtonCount;
1296                 }
1297 
1298                 if (!bRedrawDisabled)
1299                     m_TaskBar.BeginUpdate();
1300 
1301                 /* We might need to update the button spacing */
1302                 int cxButtonSpacing = m_TaskBar.UpdateTbButtonSpacing(
1303                     Horizontal, m_Theme != NULL,
1304                     uiRows, uiBtnsPerLine);
1305 
1306                 /* Determine the minimum and maximum width of a button */
1307                 uiMin = GetSystemMetrics(SM_CXSIZE) + (2 * GetSystemMetrics(SM_CXEDGE));
1308                 if (Horizontal)
1309                 {
1310                     uiMax = GetSystemMetrics(SM_CXMINIMIZED);
1311 
1312                     /* Calculate the ideal width and make sure it's within the allowed range */
1313                     NewBtnSize = (rcClient.right - (uiBtnsPerLine * cxButtonSpacing)) / uiBtnsPerLine;
1314 
1315                     if (NewBtnSize < (LONG) uiMin)
1316                         NewBtnSize = uiMin;
1317                     if (NewBtnSize >(LONG)uiMax)
1318                         NewBtnSize = uiMax;
1319 
1320                     /* Recalculate how many buttons actually fit into one line */
1321                     uiBtnsPerLine = rcClient.right / (NewBtnSize + cxButtonSpacing);
1322                     if (uiBtnsPerLine == 0)
1323                         uiBtnsPerLine++;
1324                 }
1325                 else
1326                 {
1327                     NewBtnSize = uiMax = rcClient.right;
1328                 }
1329 
1330                 m_ButtonSize.cx = NewBtnSize;
1331 
1332                 m_ButtonsPerLine = uiBtnsPerLine;
1333 
1334                 for (ui = 0; ui != m_ButtonCount; ui++)
1335                 {
1336                     TBBUTTONINFOW tbbi = { 0 };
1337                     tbbi.cbSize = sizeof(tbbi);
1338                     tbbi.dwMask = TBIF_BYINDEX | TBIF_SIZE | TBIF_STATE;
1339                     tbbi.cx = (INT) NewBtnSize;
1340                     tbbi.fsState = TBSTATE_ENABLED;
1341 
1342                     /* Check if we're updating a button that is the last one in the
1343                        line. If so, we need to set the TBSTATE_WRAP flag! */
1344                     if (Horizontal)
1345                     {
1346                         if ((ui + 1) % uiBtnsPerLine == 0)
1347                             tbbi.fsState |= TBSTATE_WRAP;
1348                     }
1349                     else
1350                     {
1351                         tbbi.fsState |= TBSTATE_WRAP;
1352                     }
1353 
1354                     if (m_ActiveTaskItem != NULL &&
1355                         m_ActiveTaskItem->Index == (INT)ui)
1356                     {
1357                         tbbi.fsState |= TBSTATE_CHECKED;
1358                     }
1359 
1360                     m_TaskBar.SetButtonInfo(ui, &tbbi);
1361                 }
1362             }
1363             else
1364             {
1365                 m_ButtonsPerLine = 0;
1366                 m_ButtonSize.cx = 0;
1367             }
1368         }
1369 
1370         // FIXME: This seems to be enabling redraws prematurely, but moving it to its right place doesn't work!
1371         m_TaskBar.EndUpdate();
1372     }
1373 
1374     BOOL CALLBACK EnumWindowsProc(IN HWND hWnd)
1375     {
1376         /* Only show windows that still exist and are visible and none of explorer's
1377         special windows (such as the desktop or the tray window) */
1378         if (::IsWindow(hWnd) && ::IsWindowVisible(hWnd) &&
1379             !m_Tray->IsSpecialHWND(hWnd))
1380         {
1381             DWORD exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE);
1382             /* Don't list popup windows and also no tool windows */
1383             if ((::GetWindow(hWnd, GW_OWNER) == NULL || exStyle & WS_EX_APPWINDOW) &&
1384                 !(exStyle & WS_EX_TOOLWINDOW))
1385             {
1386                 TRACE("Adding task for %p...\n", hWnd);
1387                 AddTask(hWnd);
1388             }
1389 
1390         }
1391 
1392         return TRUE;
1393     }
1394 
1395     static BOOL CALLBACK s_EnumWindowsProc(IN HWND hWnd, IN LPARAM lParam)
1396     {
1397         CTaskSwitchWnd * This = (CTaskSwitchWnd *) lParam;
1398 
1399         return This->EnumWindowsProc(hWnd);
1400     }
1401 
1402     BOOL RefreshWindowList()
1403     {
1404         TRACE("Refreshing window list...\n");
1405         /* Add all windows to the toolbar */
1406         return EnumWindows(s_EnumWindowsProc, (LPARAM)this);
1407     }
1408 
1409     LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1410     {
1411         TRACE("OmThemeChanged\n");
1412 
1413         if (m_Theme)
1414             CloseThemeData(m_Theme);
1415 
1416         if (IsThemeActive())
1417             m_Theme = OpenThemeData(m_hWnd, L"TaskBand");
1418         else
1419             m_Theme = NULL;
1420 
1421         return TRUE;
1422     }
1423 
1424     LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1425     {
1426         if (!m_TaskBar.Initialize(m_hWnd))
1427             return FALSE;
1428 
1429         SetWindowTheme(m_TaskBar.m_hWnd, L"TaskBand", NULL);
1430 
1431         m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
1432         m_TaskBar.SetImageList(m_ImageList);
1433 
1434         /* Set proper spacing between buttons */
1435         m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1436 
1437         /* Register the shell hook */
1438         m_ShellHookMsg = RegisterWindowMessageW(L"SHELLHOOK");
1439 
1440         TRACE("ShellHookMsg got assigned number %d\n", m_ShellHookMsg);
1441 
1442         RegisterShellHook(m_hWnd, 3); /* 1 if no NT! We're targeting NT so we don't care! */
1443 
1444         RefreshWindowList();
1445 
1446         /* Recalculate the button size */
1447         UpdateButtonsSize(FALSE);
1448 
1449 #if DUMP_TASKS != 0
1450         SetTimer(hwnd, 1, 5000, NULL);
1451 #endif
1452         return TRUE;
1453     }
1454 
1455     LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1456     {
1457         m_IsDestroying = TRUE;
1458 
1459         /* Unregister the shell hook */
1460         RegisterShellHook(m_hWnd, FALSE);
1461 
1462         CloseThemeData(m_Theme);
1463         DeleteAllTasks();
1464         return TRUE;
1465     }
1466 
1467     BOOL HandleAppCommand(IN WPARAM wParam, IN LPARAM lParam)
1468     {
1469         BOOL Ret = FALSE;
1470 
1471         switch (GET_APPCOMMAND_LPARAM(lParam))
1472         {
1473         case APPCOMMAND_BROWSER_SEARCH:
1474             Ret = SHFindFiles(NULL,
1475                 NULL);
1476             break;
1477 
1478         case APPCOMMAND_BROWSER_HOME:
1479         case APPCOMMAND_LAUNCH_MAIL:
1480         default:
1481             TRACE("Shell app command %d unhandled!\n", (INT) GET_APPCOMMAND_LPARAM(lParam));
1482             break;
1483         }
1484 
1485         return Ret;
1486     }
1487 
1488     LRESULT OnShellHook(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1489     {
1490         BOOL Ret = FALSE;
1491 
1492         /* In case the shell hook wasn't registered properly, ignore WM_NULLs*/
1493         if (uMsg == 0)
1494         {
1495             bHandled = FALSE;
1496             return 0;
1497         }
1498 
1499         TRACE("Received shell hook message: wParam=%08lx, lParam=%08lx\n", wParam, lParam);
1500 
1501         switch ((INT) wParam)
1502         {
1503         case HSHELL_APPCOMMAND:
1504             Ret = HandleAppCommand(wParam, lParam);
1505             break;
1506 
1507         case HSHELL_WINDOWCREATED:
1508             AddTask((HWND) lParam);
1509             break;
1510 
1511         case HSHELL_WINDOWDESTROYED:
1512             /* The window still exists! Delay destroying it a bit */
1513             DeleteTask((HWND) lParam);
1514             break;
1515 
1516         case HSHELL_RUDEAPPACTIVATED:
1517         case HSHELL_WINDOWACTIVATED:
1518             ActivateTask((HWND) lParam);
1519             break;
1520 
1521         case HSHELL_FLASH:
1522             FlashTask((HWND) lParam);
1523             break;
1524 
1525         case HSHELL_REDRAW:
1526             RedrawTask((HWND) lParam);
1527             break;
1528 
1529         case HSHELL_TASKMAN:
1530             ::PostMessage(m_Tray->GetHWND(), TWM_OPENSTARTMENU, 0, 0);
1531             break;
1532 
1533         case HSHELL_ACTIVATESHELLWINDOW:
1534         case HSHELL_LANGUAGE:
1535         case HSHELL_SYSMENU:
1536         case HSHELL_ENDTASK:
1537         case HSHELL_ACCESSIBILITYSTATE:
1538         case HSHELL_WINDOWREPLACED:
1539         case HSHELL_WINDOWREPLACING:
1540 
1541         case HSHELL_GETMINRECT:
1542         default:
1543         {
1544 #if DEBUG_SHELL_HOOK
1545             int i, found;
1546             for (i = 0, found = 0; i != _countof(hshell_msg); i++)
1547             {
1548                 if (hshell_msg[i].msg == (INT) wParam)
1549                 {
1550                     TRACE("Shell message %ws unhandled (lParam = 0x%p)!\n", hshell_msg[i].msg_name, lParam);
1551                     found = 1;
1552                     break;
1553                 }
1554             }
1555             if (found)
1556                 break;
1557 #endif
1558             TRACE("Shell message %d unhandled (lParam = 0x%p)!\n", (INT) wParam, lParam);
1559             break;
1560         }
1561         }
1562 
1563         return Ret;
1564     }
1565 
1566     VOID HandleTaskItemClick(IN OUT PTASK_ITEM TaskItem)
1567     {
1568         BOOL bIsMinimized;
1569         BOOL bIsActive;
1570 
1571         if (::IsWindow(TaskItem->hWnd))
1572         {
1573             bIsMinimized = ::IsIconic(TaskItem->hWnd);
1574             bIsActive = (TaskItem == m_ActiveTaskItem);
1575 
1576             TRACE("Active TaskItem %p, selected TaskItem %p\n", m_ActiveTaskItem, TaskItem);
1577             if (m_ActiveTaskItem)
1578                 TRACE("Active TaskItem hWnd=%p, TaskItem hWnd %p\n", m_ActiveTaskItem->hWnd, TaskItem->hWnd);
1579 
1580             TRACE("Valid button clicked. HWND=%p, IsMinimized=%s, IsActive=%s...\n",
1581                 TaskItem->hWnd, bIsMinimized ? "Yes" : "No", bIsActive ? "Yes" : "No");
1582 
1583             if (!bIsMinimized && bIsActive)
1584             {
1585                 ::ShowWindowAsync(TaskItem->hWnd, SW_MINIMIZE);
1586                 TRACE("Valid button clicked. App window Minimized.\n");
1587             }
1588             else
1589             {
1590                 ::SwitchToThisWindow(TaskItem->hWnd, TRUE);
1591                 TRACE("Valid button clicked. App window Restored.\n");
1592             }
1593         }
1594     }
1595 
1596     VOID HandleTaskGroupClick(IN OUT PTASK_GROUP TaskGroup)
1597     {
1598         /* TODO: Show task group menu */
1599     }
1600 
1601     BOOL HandleButtonClick(IN WORD wIndex)
1602     {
1603         PTASK_ITEM TaskItem;
1604         PTASK_GROUP TaskGroup;
1605 
1606         if (m_IsGroupingEnabled)
1607         {
1608             TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1609             if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1610             {
1611                 HandleTaskGroupClick(TaskGroup);
1612                 return TRUE;
1613             }
1614         }
1615 
1616         TaskItem = FindTaskItemByIndex((INT) wIndex);
1617         if (TaskItem != NULL)
1618         {
1619             HandleTaskItemClick(TaskItem);
1620             return TRUE;
1621         }
1622 
1623         return FALSE;
1624     }
1625 
1626 
1627     VOID HandleTaskItemRightClick(IN OUT PTASK_ITEM TaskItem)
1628     {
1629         POINT pt;
1630         GetCursorPos(&pt);
1631 
1632         SetForegroundWindow(TaskItem->hWnd);
1633 
1634         ActivateTask(TaskItem->hWnd);
1635 
1636         ::SendMessageW(TaskItem->hWnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y));
1637     }
1638 
1639     VOID HandleTaskGroupRightClick(IN OUT PTASK_GROUP TaskGroup)
1640     {
1641         /* TODO: Show task group right click menu */
1642     }
1643 
1644     BOOL HandleButtonRightClick(IN WORD wIndex)
1645     {
1646         PTASK_ITEM TaskItem;
1647         PTASK_GROUP TaskGroup;
1648         if (m_IsGroupingEnabled)
1649         {
1650             TaskGroup = FindTaskGroupByIndex((INT) wIndex);
1651             if (TaskGroup != NULL && TaskGroup->IsCollapsed)
1652             {
1653                 HandleTaskGroupRightClick(TaskGroup);
1654                 return TRUE;
1655             }
1656         }
1657 
1658         TaskItem = FindTaskItemByIndex((INT) wIndex);
1659 
1660         if (TaskItem != NULL)
1661         {
1662             HandleTaskItemRightClick(TaskItem);
1663             return TRUE;
1664         }
1665 
1666         return FALSE;
1667     }
1668 
1669 
1670     LRESULT HandleItemPaint(IN OUT NMTBCUSTOMDRAW *nmtbcd)
1671     {
1672         LRESULT Ret = CDRF_DODEFAULT;
1673         PTASK_GROUP TaskGroup;
1674         PTASK_ITEM TaskItem;
1675 
1676         TaskItem = FindTaskItemByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1677         TaskGroup = FindTaskGroupByIndex((INT) nmtbcd->nmcd.dwItemSpec);
1678         if (TaskGroup == NULL && TaskItem != NULL)
1679         {
1680             ASSERT(TaskItem != NULL);
1681 
1682             if (TaskItem != NULL && ::IsWindow(TaskItem->hWnd))
1683             {
1684                 /* Make the entire button flashing if necessary */
1685                 if (nmtbcd->nmcd.uItemState & CDIS_MARKED)
1686                 {
1687                     Ret = TBCDRF_NOBACKGROUND;
1688                     if (!m_Theme)
1689                     {
1690                         SelectObject(nmtbcd->nmcd.hdc, GetSysColorBrush(COLOR_HIGHLIGHT));
1691                         Rectangle(nmtbcd->nmcd.hdc,
1692                             nmtbcd->nmcd.rc.left,
1693                             nmtbcd->nmcd.rc.top,
1694                             nmtbcd->nmcd.rc.right,
1695                             nmtbcd->nmcd.rc.bottom);
1696                     }
1697                     else
1698                     {
1699                         DrawThemeBackground(m_Theme, nmtbcd->nmcd.hdc, TDP_FLASHBUTTON, 0, &nmtbcd->nmcd.rc, 0);
1700                     }
1701                     nmtbcd->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
1702                     return Ret;
1703                 }
1704             }
1705         }
1706         else if (TaskGroup != NULL)
1707         {
1708             /* FIXME: Implement painting for task groups */
1709         }
1710         return Ret;
1711     }
1712 
1713     LRESULT HandleToolbarNotification(IN const NMHDR *nmh)
1714     {
1715         LRESULT Ret = 0;
1716 
1717         switch (nmh->code)
1718         {
1719         case NM_CUSTOMDRAW:
1720         {
1721             LPNMTBCUSTOMDRAW nmtbcd = (LPNMTBCUSTOMDRAW) nmh;
1722 
1723             switch (nmtbcd->nmcd.dwDrawStage)
1724             {
1725 
1726             case CDDS_ITEMPREPAINT:
1727                 Ret = HandleItemPaint(nmtbcd);
1728                 break;
1729 
1730             case CDDS_PREPAINT:
1731                 Ret = CDRF_NOTIFYITEMDRAW;
1732                 break;
1733 
1734             default:
1735                 Ret = CDRF_DODEFAULT;
1736                 break;
1737             }
1738             break;
1739         }
1740         }
1741 
1742         return Ret;
1743     }
1744 
1745     LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1746     {
1747         HDC hdc = (HDC) wParam;
1748 
1749         if (!IsAppThemed())
1750         {
1751             bHandled = FALSE;
1752             return 0;
1753         }
1754 
1755         RECT rect;
1756         GetClientRect(&rect);
1757         DrawThemeParentBackground(m_hWnd, hdc, &rect);
1758 
1759         return TRUE;
1760     }
1761 
1762     LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1763     {
1764         SIZE szClient;
1765 
1766         szClient.cx = LOWORD(lParam);
1767         szClient.cy = HIWORD(lParam);
1768         if (m_TaskBar.m_hWnd != NULL)
1769         {
1770             m_TaskBar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
1771 
1772             UpdateButtonsSize(FALSE);
1773         }
1774         return TRUE;
1775     }
1776 
1777     LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1778     {
1779         LRESULT Ret = TRUE;
1780         /* We want the tray window to be draggable everywhere, so make the control
1781         appear transparent */
1782         Ret = DefWindowProc(uMsg, wParam, lParam);
1783         if (Ret != HTVSCROLL && Ret != HTHSCROLL)
1784             Ret = HTTRANSPARENT;
1785         return Ret;
1786     }
1787 
1788     LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1789     {
1790         LRESULT Ret = TRUE;
1791         if (lParam != 0 && (HWND) lParam == m_TaskBar.m_hWnd)
1792         {
1793             HandleButtonClick(LOWORD(wParam));
1794         }
1795         return Ret;
1796     }
1797 
1798     LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1799     {
1800         LRESULT Ret = TRUE;
1801         const NMHDR *nmh = (const NMHDR *) lParam;
1802 
1803         if (nmh->hwndFrom == m_TaskBar.m_hWnd)
1804         {
1805             Ret = HandleToolbarNotification(nmh);
1806         }
1807         return Ret;
1808     }
1809 
1810     LRESULT OnUpdateTaskbarPos(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1811     {
1812         /* Update the button spacing */
1813         m_TaskBar.UpdateTbButtonSpacing(m_Tray->IsHorizontal(), m_Theme != NULL);
1814         return TRUE;
1815     }
1816 
1817     LRESULT OnTaskbarSettingsChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1818     {
1819         TaskbarSettings* newSettings = (TaskbarSettings*)lParam;
1820         if (newSettings->bGroupButtons != g_TaskbarSettings.bGroupButtons)
1821         {
1822             g_TaskbarSettings.bGroupButtons = newSettings->bGroupButtons;
1823             m_IsGroupingEnabled = g_TaskbarSettings.bGroupButtons;
1824 
1825             /* Collapse or expand groups if necessary */
1826             RefreshWindowList();
1827             UpdateButtonsSize(FALSE);
1828         }
1829 
1830         return 0;
1831     }
1832 
1833     LRESULT OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1834     {
1835         LRESULT Ret = 0;
1836         INT_PTR iBtn = -1;
1837 
1838         if (m_TaskBar.m_hWnd != NULL)
1839         {
1840             POINT pt;
1841 
1842             pt.x = GET_X_LPARAM(lParam);
1843             pt.y = GET_Y_LPARAM(lParam);
1844 
1845             ::ScreenToClient(m_TaskBar.m_hWnd, &pt);
1846 
1847             iBtn = m_TaskBar.HitTest(&pt);
1848             if (iBtn >= 0)
1849             {
1850                 HandleButtonRightClick(iBtn);
1851             }
1852         }
1853         if (iBtn < 0)
1854         {
1855             /* Not on a taskbar button, so forward message to tray */
1856             Ret = SendMessage(m_Tray->GetHWND(), uMsg, wParam, lParam);
1857         }
1858         return Ret;
1859     }
1860 
1861     LRESULT OnKludgeItemRect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1862     {
1863         PTASK_ITEM TaskItem = FindTaskItem((HWND) wParam);
1864         if (TaskItem)
1865         {
1866             RECT* prcMinRect = (RECT*) lParam;
1867             RECT rcItem, rcToolbar;
1868             m_TaskBar.GetItemRect(TaskItem->Index, &rcItem);
1869             m_TaskBar.GetWindowRect(&rcToolbar);
1870 
1871             OffsetRect(&rcItem, rcToolbar.left, rcToolbar.top);
1872 
1873             *prcMinRect = rcItem;
1874             return TRUE;
1875         }
1876         return FALSE;
1877     }
1878 
1879     LRESULT OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1880     {
1881         return MA_NOACTIVATE;
1882     }
1883 
1884     LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1885     {
1886 #if DUMP_TASKS != 0
1887         switch (wParam)
1888         {
1889         case 1:
1890             DumpTasks();
1891             break;
1892         }
1893 #endif
1894         return TRUE;
1895     }
1896 
1897     LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1898     {
1899         return m_TaskBar.SendMessageW(uMsg, wParam, lParam);
1900     }
1901 
1902     LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1903     {
1904         if (wParam == SPI_SETNONCLIENTMETRICS)
1905         {
1906             /*  Don't update the font, this will be done when we get a WM_SETFONT from our parent */
1907             UpdateButtonsSize(FALSE);
1908         }
1909 
1910         return 0;
1911     }
1912 
1913     LRESULT OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1914     {
1915         PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT)lParam;
1916         if (cpData->dwData == m_uHardErrorMsg)
1917         {
1918             /* A hard error balloon message */
1919             PBALLOON_HARD_ERROR_DATA pData = (PBALLOON_HARD_ERROR_DATA)cpData->lpData;
1920             ERR("Got balloon data 0x%x, 0x%x, '%S', '%S'\n", pData->Status, pData->dwType, (WCHAR*)((ULONG_PTR)pData + pData->TitleOffset), (WCHAR*)((ULONG_PTR)pData + pData->MessageOffset));
1921             if (pData->cbHeaderSize == sizeof(BALLOON_HARD_ERROR_DATA))
1922                 m_HardErrorThread.StartThread(pData);
1923             return TRUE;
1924         }
1925 
1926         return FALSE;
1927     }
1928 
1929     HRESULT Initialize(IN HWND hWndParent, IN OUT ITrayWindow *tray)
1930     {
1931         m_Tray = tray;
1932         m_IsGroupingEnabled = g_TaskbarSettings.bGroupButtons;
1933         Create(hWndParent, 0, szRunningApps, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP);
1934         if (!m_hWnd)
1935             return E_FAIL;
1936         return S_OK;
1937     }
1938 
1939     HRESULT WINAPI GetWindow(HWND* phwnd)
1940     {
1941         if (!phwnd)
1942             return E_INVALIDARG;
1943         *phwnd = m_hWnd;
1944         return S_OK;
1945     }
1946 
1947     HRESULT WINAPI ContextSensitiveHelp(BOOL fEnterMode)
1948     {
1949         return E_NOTIMPL;
1950     }
1951 
1952     DECLARE_WND_CLASS_EX(szTaskSwitchWndClass, CS_DBLCLKS, COLOR_3DFACE)
1953 
1954     BEGIN_MSG_MAP(CTaskSwitchWnd)
1955         MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1956         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1957         MESSAGE_HANDLER(WM_SIZE, OnSize)
1958         MESSAGE_HANDLER(WM_CREATE, OnCreate)
1959         MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1960         MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1961         MESSAGE_HANDLER(WM_COMMAND, OnCommand)
1962         MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
1963         MESSAGE_HANDLER(TSWM_UPDATETASKBARPOS, OnUpdateTaskbarPos)
1964         MESSAGE_HANDLER(TWM_SETTINGSCHANGED, OnTaskbarSettingsChanged)
1965         MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
1966         MESSAGE_HANDLER(WM_TIMER, OnTimer)
1967         MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1968         MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
1969         MESSAGE_HANDLER(m_ShellHookMsg, OnShellHook)
1970         MESSAGE_HANDLER(WM_MOUSEACTIVATE, OnMouseActivate)
1971         MESSAGE_HANDLER(WM_KLUDGEMINRECT, OnKludgeItemRect)
1972         MESSAGE_HANDLER(WM_COPYDATA, OnCopyData)
1973     END_MSG_MAP()
1974 
1975     DECLARE_NOT_AGGREGATABLE(CTaskSwitchWnd)
1976 
1977     DECLARE_PROTECT_FINAL_CONSTRUCT()
1978     BEGIN_COM_MAP(CTaskSwitchWnd)
1979         COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
1980     END_COM_MAP()
1981 };
1982 
1983 HRESULT CTaskSwitchWnd_CreateInstance(IN HWND hWndParent, IN OUT ITrayWindow *Tray, REFIID riid, void **ppv)
1984 {
1985     return ShellObjectCreatorInit<CTaskSwitchWnd>(hWndParent, Tray, riid, ppv);
1986 }
1987