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 == 0)
428          return FALSE;
429 
430       if (windowCount == 1)
431       {
432          selectedWindow = 0;
433          CompleteSwitch(TRUE);
434          return TRUE;
435       }
436 
437       selectedWindow = 1;
438 
439       TRACE("[ATbot] HotKey Received. Opening window.\n");
440       ShowWindow(switchdialog, SW_SHOWNORMAL);
441       MakeWindowActive(switchdialog);
442       isOpen = TRUE;
443    }
444    else
445    {
446       TRACE("[ATbot] HotKey Received  Rotating.\n");
447       selectedWindow = (selectedWindow + 1)%windowCount;
448       InvalidateRect(switchdialog, NULL, TRUE);
449    }
450    return TRUE;
451 }
452 
453 void RotateTasks(BOOL bShift)
454 {
455     HWND hwndFirst, hwndLast;
456     DWORD Size;
457 
458     if (windowCount < 2 || !Esc)
459         return;
460 
461     hwndFirst = windowList[0];
462     hwndLast = windowList[windowCount - 1];
463 
464     if (bShift)
465     {
466         SetWindowPos(hwndLast, HWND_TOP, 0, 0, 0, 0,
467                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
468                      SWP_NOOWNERZORDER | SWP_NOREPOSITION);
469 
470         MakeWindowActive(hwndLast);
471 
472         Size = (windowCount - 1) * sizeof(HWND);
473         MoveMemory(&windowList[1], &windowList[0], Size);
474         windowList[0] = hwndLast;
475     }
476     else
477     {
478         SetWindowPos(hwndFirst, hwndLast, 0, 0, 0, 0,
479                      SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE |
480                      SWP_NOOWNERZORDER | SWP_NOREPOSITION);
481 
482         MakeWindowActive(windowList[1]);
483 
484         Size = (windowCount - 1) * sizeof(HWND);
485         MoveMemory(&windowList[0], &windowList[1], Size);
486         windowList[windowCount - 1] = hwndFirst;
487     }
488 }
489 
490 static void MoveLeft(void)
491 {
492     selectedWindow = selectedWindow - 1;
493     if (selectedWindow < 0)
494         selectedWindow = windowCount - 1;
495     InvalidateRect(switchdialog, NULL, TRUE);
496 }
497 
498 static void MoveRight(void)
499 {
500     selectedWindow = (selectedWindow + 1) % windowCount;
501     InvalidateRect(switchdialog, NULL, TRUE);
502 }
503 
504 static void MoveUp(void)
505 {
506     INT iRow = selectedWindow / nCols;
507     INT iCol = selectedWindow % nCols;
508 
509     --iRow;
510     if (iRow < 0)
511         iRow = nRows - 1;
512 
513     selectedWindow = iRow * nCols + iCol;
514     if (selectedWindow >= windowCount)
515         selectedWindow = windowCount - 1;
516     InvalidateRect(switchdialog, NULL, TRUE);
517 }
518 
519 static void MoveDown(void)
520 {
521     INT iRow = selectedWindow / nCols;
522     INT iCol = selectedWindow % nCols;
523 
524     ++iRow;
525     if (iRow >= nRows)
526         iRow = 0;
527 
528     selectedWindow = iRow * nCols + iCol;
529     if (selectedWindow >= windowCount)
530         selectedWindow = windowCount - 1;
531     InvalidateRect(switchdialog, NULL, TRUE);
532 }
533 
534 VOID
535 DestroyAppWindows(VOID)
536 {
537     // for every item of the icon list:
538     INT i;
539     for (i = 0; i < windowCount; ++i)
540     {
541         // destroy the icon
542         DestroyIcon(iconList[i]);
543         iconList[i] = NULL;
544     }
545 }
546 
547 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam )
548 {
549    HWND hwndActive;
550    MSG msg;
551 
552    // FIXME: Is loading timing OK?
553    LoadCoolSwitchSettings();
554 
555    if (!CoolSwitch)
556       return 0;
557 
558    // Already in the loop.
559    if (switchdialog || Esc) return 0;
560 
561    hwndActive = GetActiveWindow();
562    // Nothing is active so exit.
563    if (!hwndActive) return 0;
564 
565    if (lParam == VK_ESCAPE)
566    {
567       Esc = TRUE;
568 
569       windowCount = 0;
570       EnumChildWindowsZOrder(NULL, EnumerateCallback, 0);
571 
572       if (windowCount < 2)
573           return 0;
574 
575       RotateTasks(GetAsyncKeyState(VK_SHIFT) < 0);
576 
577       hwndActive = GetActiveWindow();
578 
579       if (hwndActive == NULL)
580       {
581           Esc = FALSE;
582           return 0;
583       }
584    }
585 
586    // Capture current active window.
587    SetCapture( hwndActive );
588 
589    switch (lParam)
590    {
591       case VK_TAB:
592          if( !CreateSwitcherWindow(User32Instance) ) goto Exit;
593          if( !GetDialogFont() ) goto Exit;
594          if( !ProcessHotKey() ) goto Exit;
595          break;
596 
597       case VK_ESCAPE:
598          break;
599 
600       default:
601          goto Exit;
602    }
603    // Main message loop:
604    while (1)
605    {
606       for (;;)
607       {
608          if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ))
609          {
610              if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break;
611              /* remove the message from the queue */
612              PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
613          }
614          else
615              WaitMessage();
616       }
617 
618       switch (msg.message)
619       {
620         case WM_KEYUP:
621         {
622           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
623           if (msg.wParam == VK_MENU)
624           {
625              CompleteSwitch(TRUE);
626           }
627           else if (msg.wParam == VK_RETURN)
628           {
629              CompleteSwitch(TRUE);
630           }
631           else if (msg.wParam == VK_ESCAPE)
632           {
633              TRACE("DoAppSwitch VK_ESCAPE 2\n");
634              CompleteSwitch(FALSE);
635           }
636           goto Exit; //break;
637         }
638 
639         case WM_SYSKEYDOWN:
640         {
641           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
642           if (HIWORD(msg.lParam) & KF_ALTDOWN)
643           {
644              if ( msg.wParam == VK_TAB )
645              {
646                 if (Esc) break;
647                 if (GetKeyState(VK_SHIFT) < 0)
648                 {
649                     MoveLeft();
650                 }
651                 else
652                 {
653                     MoveRight();
654                 }
655              }
656              else if ( msg.wParam == VK_ESCAPE )
657              {
658                 if (!Esc) break;
659                 RotateTasks(GetKeyState(VK_SHIFT) < 0);
660              }
661              else if ( msg.wParam == VK_LEFT )
662              {
663                 MoveLeft();
664              }
665              else if ( msg.wParam == VK_RIGHT )
666              {
667                 MoveRight();
668              }
669              else if ( msg.wParam == VK_UP )
670              {
671                 MoveUp();
672              }
673              else if ( msg.wParam == VK_DOWN )
674              {
675                 MoveDown();
676              }
677           }
678           break;
679         }
680 
681         case WM_LBUTTONUP:
682           PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE );
683           ProcessMouseMessage(msg.message, msg.lParam);
684           goto Exit;
685 
686         default:
687           if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ))
688           {
689              TranslateMessage(&msg);
690              DispatchMessageW(&msg);
691           }
692           break;
693       }
694    }
695 Exit:
696    ReleaseCapture();
697    if (switchdialog) DestroyWindow(switchdialog);
698    if (Esc) DestroyAppWindows();
699    switchdialog = NULL;
700    selectedWindow = 0;
701    windowCount = 0;
702    Esc = FALSE;
703    return 0;
704 }
705 
706 //
707 // Switch System Class Window Proc.
708 //
709 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode )
710 {
711    PWND pWnd;
712    PALTTABINFO ati;
713    pWnd = ValidateHwnd(hWnd);
714    if (pWnd)
715    {
716       if (!pWnd->fnid)
717       {
718          NtUserSetWindowFNID(hWnd, FNID_SWITCH);
719       }
720    }
721 
722    switch (uMsg)
723    {
724       case WM_NCCREATE:
725          if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati))))
726             return 0;
727          SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati );
728          return TRUE;
729 
730       case WM_SHOWWINDOW:
731          if (wParam)
732          {
733             PrepareWindow();
734             ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
735             ati->cbSize = sizeof(ALTTABINFO);
736             ati->cItems = nItems;
737             ati->cColumns = nCols;
738             ati->cRows = nRows;
739             if (nCols)
740             {
741                ati->iColFocus = (selectedWindow - nShift) % nCols;
742                ati->iRowFocus = (selectedWindow - nShift) / nCols;
743             }
744             else
745             {
746                ati->iColFocus = 0;
747                ati->iRowFocus = 0;
748             }
749             ati->cxItem = CX_ITEM_SPACE;
750             ati->cyItem = CY_ITEM_SPACE;
751             ati->ptStart = ptStart;
752          }
753          return 0;
754 
755       case WM_MOUSEMOVE:
756          ProcessMouseMessage(uMsg, lParam);
757          return 0;
758 
759       case WM_ACTIVATE:
760          if (wParam == WA_INACTIVE)
761          {
762             CompleteSwitch(FALSE);
763          }
764          return 0;
765 
766       case WM_PAINT:
767          OnPaint(hWnd);
768          return 0;
769 
770       case WM_DESTROY:
771          isOpen = FALSE;
772          ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0);
773          HeapFree( GetProcessHeap(), 0, ati );
774          SetWindowLongPtrW( hWnd, 0, 0 );
775          DestroyAppWindows();
776          NtUserSetWindowFNID(hWnd, FNID_DESTROY);
777          return 0;
778    }
779    return DefWindowProcW(hWnd, uMsg, wParam, lParam);
780 }
781 
782 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
783 {
784    return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE);
785 }
786 
787 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
788 {
789    return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE);
790 }
791