1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            win32ss/user/user32/controls/appswitch.c
5  * PURPOSE:         app switching functionality
6  * PROGRAMMERS:     Johannes Anderwald (johannes.anderwald@reactos.org)
7  *                  David Quintana (gigaherz@gmail.com)
8  *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
9  */
10 
11 //
12 // TODO:
13 //       Move to Win32k.
14 //       Add registry support.
15 //
16 //
17 
18 #include <user32.h>
19 
20 WINE_DEFAULT_DEBUG_CHANNEL(user32);
21 
22 #define DIALOG_MARGIN   8       // margin of dialog contents
23 
24 #define CX_ICON         32      // width of icon
25 #define CY_ICON         32      // height of icon
26 #define ICON_MARGIN     4       // margin width around an icon
27 
28 #define CX_ITEM         (CX_ICON + 2 * ICON_MARGIN)
29 #define CY_ITEM         (CY_ICON + 2 * ICON_MARGIN)
30 #define ITEM_MARGIN     4       // margin width around an item
31 
32 #define CX_ITEM_SPACE   (CX_ITEM + 2 * ITEM_MARGIN)
33 #define CY_ITEM_SPACE   (CY_ITEM + 2 * ITEM_MARGIN)
34 
35 #define CY_TEXT_MARGIN  4       // margin height around text
36 
37 // limit the number of windows shown in the alt-tab window
38 // 120 windows results in (12*40) by (10*40) pixels worth of icons.
39 #define MAX_WINDOWS 120
40 
41 // Global variables
42 HWND switchdialog = NULL;
43 HFONT dialogFont;
44 int selectedWindow = 0;
45 BOOL isOpen = FALSE;
46 
47 int fontHeight=0;
48 
49 WCHAR windowText[1024];
50 
51 HWND windowList[MAX_WINDOWS];
52 HICON iconList[MAX_WINDOWS];
53 int windowCount = 0;
54 
55 int cxBorder, cyBorder;
56 int nItems, nCols, nRows;
57 int itemsW, itemsH;
58 int totalW, totalH;
59 int xOffset, yOffset;
60 POINT ptStart;
61 
62 int nShift = 0;
63 
64 BOOL Esc = FALSE;
65 
66 BOOL CoolSwitch = TRUE;
67 int CoolSwitchRows = 3;
68 int CoolSwitchColumns = 7;
69 
70 // window style
71 const DWORD Style = WS_POPUP | WS_BORDER | WS_DISABLED;
72 const DWORD ExStyle = WS_EX_TOPMOST | WS_EX_DLGMODALFRAME | WS_EX_TOOLWINDOW;
73 
74 DWORD wtodw(const WCHAR *psz)
75 {
76    const WCHAR *pch = psz;
77    DWORD Value = 0;
78    while ('0' <= *pch && *pch <= '9')
79    {
80       Value *= 10;
81       Value += *pch - L'0';
82    }
83    return Value;
84 }
85 
86 BOOL LoadCoolSwitchSettings(void)
87 {
88    CoolSwitch = TRUE;
89    CoolSwitchRows = 3;
90    CoolSwitchColumns = 7;
91 
92    // FIXME: load the settings from registry
93 
94    TRACE("CoolSwitch: %d\n", CoolSwitch);
95    TRACE("CoolSwitchRows: %d\n", CoolSwitchRows);
96    TRACE("CoolSwitchColumns: %d\n", CoolSwitchColumns);
97 
98    return TRUE;
99 }
100 
101 void ResizeAndCenter(HWND hwnd, int width, int height)
102 {
103    int x, y;
104    RECT Rect;
105 
106    int screenwidth = GetSystemMetrics(SM_CXSCREEN);
107    int screenheight = GetSystemMetrics(SM_CYSCREEN);
108 
109    x = (screenwidth - width) / 2;
110    y = (screenheight - height) / 2;
111 
112    SetRect(&Rect, x, y, x + width, y + height);
113    AdjustWindowRectEx(&Rect, Style, FALSE, ExStyle);
114 
115    x = Rect.left;
116    y = Rect.top;
117    width = Rect.right - Rect.left;
118    height = Rect.bottom - Rect.top;
119    MoveWindow(hwnd, x, y, width, height, FALSE);
120 
121    ptStart.x = x;
122    ptStart.y = y;
123 }
124 
125 void MakeWindowActive(HWND hwnd)
126 {
127    if (IsIconic(hwnd))
128       PostMessageW(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
129 
130    BringWindowToTop(hwnd);  // same as: SetWindowPos(hwnd,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); ?
131    SetForegroundWindow(hwnd);
132 }
133 
134 void CompleteSwitch(BOOL doSwitch)
135 {
136    if (!isOpen)
137       return;
138 
139    isOpen = FALSE;
140 
141    TRACE("[ATbot] CompleteSwitch Hiding Window.\n");
142    ShowWindow(switchdialog, SW_HIDE);
143 
144    if(doSwitch)
145    {
146       if(selectedWindow >= windowCount)
147          return;
148 
149       // FIXME: workaround because reactos fails to activate the previous window correctly.
150       //if(selectedWindow != 0)
151       {
152          HWND hwnd = windowList[selectedWindow];
153 
154          GetWindowTextW(hwnd, windowText, _countof(windowText));
155 
156          TRACE("[ATbot] CompleteSwitch Switching to 0x%08x (%ls)\n", hwnd, windowText);
157 
158          MakeWindowActive(hwnd);
159       }
160    }
161 
162    windowCount = 0;
163 }
164 
165 BOOL CALLBACK EnumerateCallback(HWND window, LPARAM lParam)
166 {
167    HICON hIcon;
168    HWND hwndOwner;
169 
170    UNREFERENCED_PARAMETER(lParam);
171 
172    if (!IsWindowVisible(window))
173             return TRUE;
174 
175    hwndOwner = GetWindow(window, GW_OWNER);
176    if (hwndOwner && IsWindowVisible(hwndOwner))
177        return TRUE;
178 
179    GetClassNameW(window, windowText, _countof(windowText));
180    if ((wcscmp(L"Shell_TrayWnd", windowText)==0) ||
181        (wcscmp(L"Progman", windowText)==0) )
182             return TRUE;
183 
184    // First try to get the big icon assigned to the window
185    hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_BIG, 0);
186    if (!hIcon)
187    {
188       // If no icon is assigned, try to get the icon assigned to the windows' class
189       hIcon = (HICON)GetClassLongPtrW(window, GCL_HICON);
190       if (!hIcon)
191       {
192          // If we still don't have an icon, see if we can do with the small icon,
193          // or a default application icon
194          hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_SMALL2, 0);
195          if (!hIcon)
196          {
197             // using windows logo icon as default
198             hIcon = gpsi->hIconWindows;
199             if (!hIcon)
200             {
201                //if all attempts to get icon fails go to the next window
202                return TRUE;
203             }
204          }
205       }
206    }
207 
208    windowList[windowCount] = window;
209    iconList[windowCount] = CopyIcon(hIcon);
210 
211    windowCount++;
212 
213    // If we got to the max number of windows,
214    // we won't be able to add any more
215    if(windowCount >= MAX_WINDOWS)
216       return FALSE;
217 
218    return TRUE;
219 }
220 
221 // Function mostly compatible with the normal EnumChildWindows,
222 // except it lists in Z-Order and it doesn't ensure consistency
223 // if a window is removed while enumerating
224 void EnumChildWindowsZOrder(HWND hwnd, WNDENUMPROC callback, LPARAM lParam)
225 {
226     HWND next = GetTopWindow(hwnd);
227     while (next != NULL)
228     {
229         if (!hwnd && !IsWindowVisible(next))
230         {
231             // UPDATE: Seek also the owned windows of the hidden top-level window.
232             EnumChildWindowsZOrder(next, callback, lParam);
233         }
234 
235         if (!callback(next, lParam))
236             break;
237 
238         next = GetWindow(next, GW_HWNDNEXT);
239     }
240 }
241 
242 void ProcessMouseMessage(UINT message, LPARAM lParam)
243 {
244    int xPos = LOWORD(lParam);
245    int yPos = HIWORD(lParam);
246 
247    int xIndex = (xPos - DIALOG_MARGIN) / CX_ITEM_SPACE;
248    int yIndex = (yPos - DIALOG_MARGIN) / CY_ITEM_SPACE;
249 
250    if (xIndex < 0 || nCols <= xIndex ||
251        yIndex < 0 || nRows <= yIndex)
252    {
253         return;
254    }
255 
256    selectedWindow = (yIndex*nCols) + xIndex;
257    if (message == WM_MOUSEMOVE)
258    {
259       InvalidateRect(switchdialog, NULL, TRUE);
260       //RedrawWindow(switchdialog, NULL, NULL, 0);
261    }
262    else
263    {
264       selectedWindow = (yIndex*nCols) + xIndex;
265       CompleteSwitch(TRUE);
266    }
267 }
268 
269 void OnPaint(HWND hWnd)
270 {
271     HDC dialogDC;
272     PAINTSTRUCT paint;
273     RECT cRC, textRC;
274     int i, xPos, yPos, CharCount;
275     HFONT dcFont;
276     HICON hIcon;
277     HPEN hPen;
278     COLORREF Color;
279 
280     // check
281     if (nCols == 0 || nItems == 0)
282         return;
283 
284     // begin painting
285     dialogDC = BeginPaint(hWnd, &paint);
286     if (dialogDC == NULL)
287         return;
288 
289     // fill the client area
290     GetClientRect(hWnd, &cRC);
291     FillRect(dialogDC, &cRC, (HBRUSH)(COLOR_3DFACE + 1));
292 
293     // if the selection index exceeded the display items, then
294     // do display item shifting
295     if (selectedWindow >= nItems)
296         nShift = selectedWindow - nItems + 1;
297     else
298         nShift = 0;
299 
300     for (i = 0; i < nItems; ++i)
301     {
302         // get the icon to display
303         hIcon = iconList[i + nShift];
304 
305         // calculate the position where we start drawing
306         xPos = DIALOG_MARGIN + CX_ITEM_SPACE * (i % nCols) + ITEM_MARGIN;
307         yPos = DIALOG_MARGIN + CY_ITEM_SPACE * (i / nCols) + ITEM_MARGIN;
308 
309         // centering
310         if (nItems < CoolSwitchColumns)
311         {
312             xPos += (itemsW - nItems * CX_ITEM_SPACE) / 2;
313         }
314 
315         // if this position is selected,
316         if (selectedWindow == i + nShift)
317         {
318             // create a solid pen
319             Color = GetSysColor(COLOR_HIGHLIGHT);
320             hPen = CreatePen(PS_SOLID, 1, Color);
321 
322             // draw a rectangle with using the pen
323             SelectObject(dialogDC, hPen);
324             SelectObject(dialogDC, GetStockObject(NULL_BRUSH));
325             Rectangle(dialogDC, xPos, yPos, xPos + CX_ITEM, yPos + CY_ITEM);
326             Rectangle(dialogDC, xPos + 1, yPos + 1,
327                                 xPos + CX_ITEM - 1, yPos + CY_ITEM - 1);
328 
329             // delete the pen
330             DeleteObject(hPen);
331         }
332 
333         // draw icon
334         DrawIconEx(dialogDC, xPos + ICON_MARGIN, yPos + ICON_MARGIN,
335                    hIcon, CX_ICON, CY_ICON, 0, NULL, DI_NORMAL);
336     }
337 
338     // set the text rectangle
339     SetRect(&textRC, DIALOG_MARGIN, DIALOG_MARGIN + itemsH,
340             totalW - DIALOG_MARGIN, totalH - DIALOG_MARGIN);
341 
342     // draw the sunken button around text
343     DrawFrameControl(dialogDC, &textRC, DFC_BUTTON,
344                      DFCS_BUTTONPUSH | DFCS_PUSHED);
345 
346     // get text
347     CharCount = GetWindowTextW(windowList[selectedWindow], windowText,
348                                _countof(windowText));
349 
350     // draw text
351     dcFont = SelectObject(dialogDC, dialogFont);
352     SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT));
353     SetBkMode(dialogDC, TRANSPARENT);
354     DrawTextW(dialogDC, windowText, CharCount, &textRC,
355               DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE);
356     SelectObject(dialogDC, dcFont);
357 
358     // end painting
359     EndPaint(hWnd, &paint);
360 }
361 
362 DWORD CreateSwitcherWindow(HINSTANCE hInstance)
363 {
364     switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW,
365                                     WC_SWITCH,
366                                     L"",
367                                     WS_POPUP|WS_BORDER|WS_DISABLED,
368                                     CW_USEDEFAULT,
369                                     CW_USEDEFAULT,
370                                     400, 150,
371                                     NULL, NULL,
372                                     hInstance, NULL);
373     if (!switchdialog)
374     {
375        TRACE("[ATbot] Task Switcher Window failed to create.\n");
376        return 0;
377     }
378 
379     isOpen = FALSE;
380     return 1;
381 }
382 
383 DWORD GetDialogFont(VOID)
384 {
385    HDC tDC;
386    TEXTMETRIC tm;
387 
388    dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
389 
390    tDC = GetDC(0);
391    GetTextMetrics(tDC, &tm);
392    fontHeight = tm.tmHeight;
393    ReleaseDC(0, tDC);
394 
395    return 1;
396 }
397 
398 void PrepareWindow(VOID)
399 {
400    nItems = windowCount;
401 
402    nCols = CoolSwitchColumns;
403    nRows = (nItems + CoolSwitchColumns - 1) / CoolSwitchColumns;
404    if (nRows > CoolSwitchRows)
405    {
406       nRows = CoolSwitchRows;
407       nItems = nRows * nCols;
408    }
409 
410    itemsW = nCols * CX_ITEM_SPACE;
411    itemsH = nRows * CY_ITEM_SPACE;
412 
413    totalW = itemsW + 2 * DIALOG_MARGIN;
414    totalH = itemsH + 2 * DIALOG_MARGIN;
415    totalH += fontHeight + 2 * CY_TEXT_MARGIN;
416 
417    ResizeAndCenter(switchdialog, totalW, totalH);
418 }
419 
420 BOOL ProcessHotKey(VOID)
421 {
422    if (!isOpen)
423    {
424       windowCount=0;
425       EnumChildWindowsZOrder(NULL, EnumerateCallback, 0);
426 
427       if (windowCount < 2)
428          return FALSE;
429 
430       selectedWindow = 1;
431 
432       TRACE("[ATbot] HotKey Received. Opening window.\n");
433       ShowWindow(switchdialog, SW_SHOWNORMAL);
434       MakeWindowActive(switchdialog);
435       isOpen = TRUE;
436    }
437    else
438    {
439       TRACE("[ATbot] HotKey Received  Rotating.\n");
440       selectedWindow = (selectedWindow + 1)%windowCount;
441       InvalidateRect(switchdialog, NULL, TRUE);
442    }
443    return TRUE;
444 }
445 
446 void RotateTasks(BOOL bShift)
447 {
448     HWND hwndFirst, hwndLast;
449     DWORD Size;
450 
451     if (windowCount < 2 || !Esc)
452         return;
453 
454     hwndFirst = windowList[0];
455     hwndLast = windowList[windowCount - 1];
456 
457     if (bShift)
458     {
459         SetWindowPos(hwndLast, HWND_TOP, 0, 0, 0, 0,
460                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
461                      SWP_NOOWNERZORDER | SWP_NOREPOSITION);
462 
463         MakeWindowActive(hwndLast);
464 
465         Size = (windowCount - 1) * sizeof(HWND);
466         MoveMemory(&windowList[1], &windowList[0], Size);
467         windowList[0] = hwndLast;
468     }
469     else
470     {
471         SetWindowPos(hwndFirst, hwndLast, 0, 0, 0, 0,
472                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
473                      SWP_NOOWNERZORDER | SWP_NOREPOSITION);
474 
475         MakeWindowActive(windowList[1]);
476 
477         Size = (windowCount - 1) * sizeof(HWND);
478         MoveMemory(&windowList[0], &windowList[1], Size);
479         windowList[windowCount - 1] = hwndFirst;
480     }
481 }
482 
483 static void MoveLeft(void)
484 {
485     selectedWindow = selectedWindow - 1;
486     if (selectedWindow < 0)
487         selectedWindow = windowCount - 1;
488     InvalidateRect(switchdialog, NULL, TRUE);
489 }
490 
491 static void MoveRight(void)
492 {
493     selectedWindow = (selectedWindow + 1) % windowCount;
494     InvalidateRect(switchdialog, NULL, TRUE);
495 }
496 
497 static void MoveUp(void)
498 {
499     INT iRow = selectedWindow / nCols;
500     INT iCol = selectedWindow % nCols;
501 
502     --iRow;
503     if (iRow < 0)
504         iRow = nRows - 1;
505 
506     selectedWindow = iRow * nCols + iCol;
507     if (selectedWindow >= windowCount)
508         selectedWindow = windowCount - 1;
509     InvalidateRect(switchdialog, NULL, TRUE);
510 }
511 
512 static void MoveDown(void)
513 {
514     INT iRow = selectedWindow / nCols;
515     INT iCol = selectedWindow % nCols;
516 
517     ++iRow;
518     if (iRow >= nRows)
519         iRow = 0;
520 
521     selectedWindow = iRow * nCols + iCol;
522     if (selectedWindow >= windowCount)
523         selectedWindow = windowCount - 1;
524     InvalidateRect(switchdialog, NULL, TRUE);
525 }
526 
527 VOID
528 DestroyAppWindows(VOID)
529 {
530     // for every item of the icon list:
531     INT i;
532     for (i = 0; i < windowCount; ++i)
533     {
534         // destroy the icon
535         DestroyIcon(iconList[i]);
536         iconList[i] = NULL;
537     }
538 }
539 
540 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam )
541 {
542    HWND hwndActive;
543    MSG msg;
544 
545    // FIXME: Is loading timing OK?
546    LoadCoolSwitchSettings();
547 
548    if (!CoolSwitch)
549       return 0;
550 
551    // Already in the loop.
552    if (switchdialog || Esc) return 0;
553 
554    hwndActive = GetActiveWindow();
555    // Nothing is active so exit.
556    if (!hwndActive) return 0;
557 
558    if (lParam == VK_ESCAPE)
559    {
560       Esc = TRUE;
561 
562       windowCount = 0;
563       EnumChildWindowsZOrder(NULL, EnumerateCallback, 0);
564 
565       if (windowCount < 2)
566           return 0;
567 
568       RotateTasks(GetAsyncKeyState(VK_SHIFT) < 0);
569 
570       hwndActive = GetActiveWindow();
571 
572       if (hwndActive == NULL)
573       {
574           Esc = FALSE;
575           return 0;
576       }
577    }
578 
579    // Capture current active window.
580    SetCapture( hwndActive );
581 
582    switch (lParam)
583    {
584       case VK_TAB:
585          if( !CreateSwitcherWindow(User32Instance) ) goto Exit;
586          if( !GetDialogFont() ) goto Exit;
587          if( !ProcessHotKey() ) goto Exit;
588          break;
589 
590       case VK_ESCAPE:
591          break;
592 
593       default:
594          goto Exit;
595    }
596    // Main message loop:
597    while (1)
598    {
599       for (;;)
600       {
601          if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
602          {
603              if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
604              /* remove the message from the queue */
605              PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
606          }
607          else
608              WaitMessage();
609       }
610 
611       switch (msg.message)
612       {
613         case WM_KEYUP:
614         {
615           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
616           if (msg.wParam == VK_MENU)
617           {
618              CompleteSwitch(TRUE);
619           }
620           else if (msg.wParam == VK_RETURN)
621           {
622              CompleteSwitch(TRUE);
623           }
624           else if (msg.wParam == VK_ESCAPE)
625           {
626              TRACE("DoAppSwitch VK_ESCAPE 2\n");
627              CompleteSwitch(FALSE);
628           }
629           goto Exit; //break;
630         }
631 
632         case WM_SYSKEYDOWN:
633         {
634           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
635           if (HIWORD(msg.lParam) & KF_ALTDOWN)
636           {
637              if ( msg.wParam == VK_TAB )
638              {
639                 if (Esc) break;
640                 if (GetKeyState(VK_SHIFT) < 0)
641                 {
642                     MoveLeft();
643                 }
644                 else
645                 {
646                     MoveRight();
647                 }
648              }
649              else if ( msg.wParam == VK_ESCAPE )
650              {
651                 if (!Esc) break;
652                 RotateTasks(GetKeyState(VK_SHIFT) < 0);
653              }
654              else if ( msg.wParam == VK_LEFT )
655              {
656                 MoveLeft();
657              }
658              else if ( msg.wParam == VK_RIGHT )
659              {
660                 MoveRight();
661              }
662              else if ( msg.wParam == VK_UP )
663              {
664                 MoveUp();
665              }
666              else if ( msg.wParam == VK_DOWN )
667              {
668                 MoveDown();
669              }
670           }
671           break;
672         }
673 
674         case WM_LBUTTONUP:
675           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
676           ProcessMouseMessage(msg.message, msg.lParam);
677           goto Exit;
678 
679         default:
680           if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
681           {
682              TranslateMessage(&msg);
683              DispatchMessageW(&msg);
684           }
685           break;
686       }
687    }
688 Exit:
689    ReleaseCapture();
690    if (switchdialog) DestroyWindow(switchdialog);
691    if (Esc) DestroyAppWindows();
692    switchdialog = NULL;
693    selectedWindow = 0;
694    windowCount = 0;
695    Esc = FALSE;
696    return 0;
697 }
698 
699 //
700 // Switch System Class Window Proc.
701 //
702 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
703 {
704    PWND pWnd;
705    PALTTABINFO ati;
706    pWnd = ValidateHwnd(hWnd);
707    if (pWnd)
708    {
709       if (!pWnd->fnid)
710       {
711          NtUserSetWindowFNID(hWnd, FNID_SWITCH);
712       }
713    }
714 
715    switch (uMsg)
716    {
717       case WM_NCCREATE:
718          if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
719             return 0;
720          SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
721          return TRUE;
722 
723       case WM_SHOWWINDOW:
724          if (wParam)
725          {
726             PrepareWindow();
727             ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
728             ati->cbSize = sizeof(ALTTABINFO);
729             ati->cItems = nItems;
730             ati->cColumns = nCols;
731             ati->cRows = nRows;
732             if (nCols)
733             {
734                ati->iColFocus = (selectedWindow - nShift) % nCols;
735                ati->iRowFocus = (selectedWindow - nShift) / nCols;
736             }
737             else
738             {
739                ati->iColFocus = 0;
740                ati->iRowFocus = 0;
741             }
742             ati->cxItem = CX_ITEM_SPACE;
743             ati->cyItem = CY_ITEM_SPACE;
744             ati->ptStart = ptStart;
745          }
746          return 0;
747 
748       case WM_MOUSEMOVE:
749          ProcessMouseMessage(uMsg, lParam);
750          return 0;
751 
752       case WM_ACTIVATE:
753          if (wParam == WA_INACTIVE)
754          {
755             CompleteSwitch(FALSE);
756          }
757          return 0;
758 
759       case WM_PAINT:
760          OnPaint(hWnd);
761          return 0;
762 
763       case WM_DESTROY:
764          isOpen = FALSE;
765          ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
766          HeapFree( GetProcessHeap(), 0, ati );
767          SetWindowLongPtrW( hWnd, 0, 0 );
768          DestroyAppWindows();
769          NtUserSetWindowFNID(hWnd, FNID_DESTROY);
770          return 0;
771    }
772    return DefWindowProcW(hWnd, uMsg, wParam, lParam);
773 }
774 
775 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
776 {
777    return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
778 }
779 
780 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
781 {
782    return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
783 }
784