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 ShowWindowAsync(hwnd, SW_RESTORE); 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 169 UNREFERENCED_PARAMETER(lParam); 170 171 if (!IsWindowVisible(window)) 172 return TRUE; 173 174 GetClassNameW(window, windowText, _countof(windowText)); 175 if ((wcscmp(L"Shell_TrayWnd", windowText)==0) || 176 (wcscmp(L"Progman", windowText)==0) ) 177 return TRUE; 178 179 // First try to get the big icon assigned to the window 180 hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_BIG, 0); 181 if (!hIcon) 182 { 183 // If no icon is assigned, try to get the icon assigned to the windows' class 184 hIcon = (HICON)GetClassLongPtrW(window, GCL_HICON); 185 if (!hIcon) 186 { 187 // If we still don't have an icon, see if we can do with the small icon, 188 // or a default application icon 189 hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_SMALL2, 0); 190 if (!hIcon) 191 { 192 // using windows logo icon as default 193 hIcon = gpsi->hIconWindows; 194 if (!hIcon) 195 { 196 //if all attempts to get icon fails go to the next window 197 return TRUE; 198 } 199 } 200 } 201 } 202 203 windowList[windowCount] = window; 204 iconList[windowCount] = CopyIcon(hIcon); 205 206 windowCount++; 207 208 // If we got to the max number of windows, 209 // we won't be able to add any more 210 if(windowCount >= MAX_WINDOWS) 211 return FALSE; 212 213 return TRUE; 214 } 215 216 // Function mostly compatible with the normal EnumWindows, 217 // except it lists in Z-Order and it doesn't ensure consistency 218 // if a window is removed while enumerating 219 void EnumWindowsZOrder(WNDENUMPROC callback, LPARAM lParam) 220 { 221 HWND next = GetTopWindow(NULL); 222 while (next != NULL) 223 { 224 if(!callback(next, lParam)) 225 break; 226 next = GetWindow(next, GW_HWNDNEXT); 227 } 228 } 229 230 void ProcessMouseMessage(UINT message, LPARAM lParam) 231 { 232 int xPos = LOWORD(lParam); 233 int yPos = HIWORD(lParam); 234 235 int xIndex = (xPos - DIALOG_MARGIN) / CX_ITEM_SPACE; 236 int yIndex = (yPos - DIALOG_MARGIN) / CY_ITEM_SPACE; 237 238 if (xIndex < 0 || nCols <= xIndex || 239 yIndex < 0 || nRows <= yIndex) 240 { 241 return; 242 } 243 244 selectedWindow = (yIndex*nCols) + xIndex; 245 if (message == WM_MOUSEMOVE) 246 { 247 InvalidateRect(switchdialog, NULL, TRUE); 248 //RedrawWindow(switchdialog, NULL, NULL, 0); 249 } 250 else 251 { 252 selectedWindow = (yIndex*nCols) + xIndex; 253 CompleteSwitch(TRUE); 254 } 255 } 256 257 void OnPaint(HWND hWnd) 258 { 259 HDC dialogDC; 260 PAINTSTRUCT paint; 261 RECT cRC, textRC; 262 int i, xPos, yPos, CharCount; 263 HFONT dcFont; 264 HICON hIcon; 265 HPEN hPen; 266 COLORREF Color; 267 268 // check 269 if (nCols == 0 || nItems == 0) 270 return; 271 272 // begin painting 273 dialogDC = BeginPaint(hWnd, &paint); 274 if (dialogDC == NULL) 275 return; 276 277 // fill the client area 278 GetClientRect(hWnd, &cRC); 279 FillRect(dialogDC, &cRC, (HBRUSH)(COLOR_3DFACE + 1)); 280 281 // if the selection index exceeded the display items, then 282 // do display item shifting 283 if (selectedWindow >= nItems) 284 nShift = selectedWindow - nItems + 1; 285 else 286 nShift = 0; 287 288 for (i = 0; i < nItems; ++i) 289 { 290 // get the icon to display 291 hIcon = iconList[i + nShift]; 292 293 // calculate the position where we start drawing 294 xPos = DIALOG_MARGIN + CX_ITEM_SPACE * (i % nCols) + ITEM_MARGIN; 295 yPos = DIALOG_MARGIN + CY_ITEM_SPACE * (i / nCols) + ITEM_MARGIN; 296 297 // centering 298 if (nItems < CoolSwitchColumns) 299 { 300 xPos += (itemsW - nItems * CX_ITEM_SPACE) / 2; 301 } 302 303 // if this position is selected, 304 if (selectedWindow == i + nShift) 305 { 306 // create a solid pen 307 Color = GetSysColor(COLOR_HIGHLIGHT); 308 hPen = CreatePen(PS_SOLID, 1, Color); 309 310 // draw a rectangle with using the pen 311 SelectObject(dialogDC, hPen); 312 SelectObject(dialogDC, GetStockObject(NULL_BRUSH)); 313 Rectangle(dialogDC, xPos, yPos, xPos + CX_ITEM, yPos + CY_ITEM); 314 Rectangle(dialogDC, xPos + 1, yPos + 1, 315 xPos + CX_ITEM - 1, yPos + CY_ITEM - 1); 316 317 // delete the pen 318 DeleteObject(hPen); 319 } 320 321 // draw icon 322 DrawIconEx(dialogDC, xPos + ICON_MARGIN, yPos + ICON_MARGIN, 323 hIcon, CX_ICON, CY_ICON, 0, NULL, DI_NORMAL); 324 } 325 326 // set the text rectangle 327 SetRect(&textRC, DIALOG_MARGIN, DIALOG_MARGIN + itemsH, 328 totalW - DIALOG_MARGIN, totalH - DIALOG_MARGIN); 329 330 // draw the sunken button around text 331 DrawFrameControl(dialogDC, &textRC, DFC_BUTTON, 332 DFCS_BUTTONPUSH | DFCS_PUSHED); 333 334 // get text 335 CharCount = GetWindowTextW(windowList[selectedWindow], windowText, 336 _countof(windowText)); 337 338 // draw text 339 dcFont = SelectObject(dialogDC, dialogFont); 340 SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT)); 341 SetBkMode(dialogDC, TRANSPARENT); 342 DrawTextW(dialogDC, windowText, CharCount, &textRC, 343 DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE); 344 SelectObject(dialogDC, dcFont); 345 346 // end painting 347 EndPaint(hWnd, &paint); 348 } 349 350 DWORD CreateSwitcherWindow(HINSTANCE hInstance) 351 { 352 switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW, 353 WC_SWITCH, 354 L"", 355 WS_POPUP|WS_BORDER|WS_DISABLED, 356 CW_USEDEFAULT, 357 CW_USEDEFAULT, 358 400, 150, 359 NULL, NULL, 360 hInstance, NULL); 361 if (!switchdialog) 362 { 363 TRACE("[ATbot] Task Switcher Window failed to create.\n"); 364 return 0; 365 } 366 367 isOpen = FALSE; 368 return 1; 369 } 370 371 DWORD GetDialogFont(VOID) 372 { 373 HDC tDC; 374 TEXTMETRIC tm; 375 376 dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); 377 378 tDC = GetDC(0); 379 GetTextMetrics(tDC, &tm); 380 fontHeight = tm.tmHeight; 381 ReleaseDC(0, tDC); 382 383 return 1; 384 } 385 386 void PrepareWindow(VOID) 387 { 388 nItems = windowCount; 389 390 nCols = CoolSwitchColumns; 391 nRows = (nItems + CoolSwitchColumns - 1) / CoolSwitchColumns; 392 if (nRows > CoolSwitchRows) 393 { 394 nRows = CoolSwitchRows; 395 nItems = nRows * nCols; 396 } 397 398 itemsW = nCols * CX_ITEM_SPACE; 399 itemsH = nRows * CY_ITEM_SPACE; 400 401 totalW = itemsW + 2 * DIALOG_MARGIN; 402 totalH = itemsH + 2 * DIALOG_MARGIN; 403 totalH += fontHeight + 2 * CY_TEXT_MARGIN; 404 405 ResizeAndCenter(switchdialog, totalW, totalH); 406 } 407 408 BOOL ProcessHotKey(VOID) 409 { 410 if (!isOpen) 411 { 412 windowCount=0; 413 EnumWindowsZOrder(EnumerateCallback, 0); 414 415 if (windowCount < 2) 416 return FALSE; 417 418 selectedWindow = 1; 419 420 TRACE("[ATbot] HotKey Received. Opening window.\n"); 421 ShowWindow(switchdialog, SW_SHOWNORMAL); 422 MakeWindowActive(switchdialog); 423 isOpen = TRUE; 424 } 425 else 426 { 427 TRACE("[ATbot] HotKey Received Rotating.\n"); 428 selectedWindow = (selectedWindow + 1)%windowCount; 429 InvalidateRect(switchdialog, NULL, TRUE); 430 } 431 return TRUE; 432 } 433 434 void RotateTasks(BOOL bShift) 435 { 436 HWND hwndFirst, hwndLast; 437 DWORD Size; 438 439 if (windowCount < 2 || !Esc) 440 return; 441 442 hwndFirst = windowList[0]; 443 hwndLast = windowList[windowCount - 1]; 444 445 if (bShift) 446 { 447 SetWindowPos(hwndLast, HWND_TOP, 0, 0, 0, 0, 448 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | 449 SWP_NOOWNERZORDER | SWP_NOREPOSITION); 450 451 MakeWindowActive(hwndLast); 452 453 Size = (windowCount - 1) * sizeof(HWND); 454 MoveMemory(&windowList[1], &windowList[0], Size); 455 windowList[0] = hwndLast; 456 } 457 else 458 { 459 SetWindowPos(hwndFirst, hwndLast, 0, 0, 0, 0, 460 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | 461 SWP_NOOWNERZORDER | SWP_NOREPOSITION); 462 463 MakeWindowActive(windowList[1]); 464 465 Size = (windowCount - 1) * sizeof(HWND); 466 MoveMemory(&windowList[0], &windowList[1], Size); 467 windowList[windowCount - 1] = hwndFirst; 468 } 469 } 470 471 VOID 472 DestroyAppWindows(VOID) 473 { 474 // for every item of the icon list: 475 INT i; 476 for (i = 0; i < windowCount; ++i) 477 { 478 // destroy the icon 479 DestroyIcon(iconList[i]); 480 iconList[i] = NULL; 481 } 482 } 483 484 LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam ) 485 { 486 HWND hwndActive; 487 MSG msg; 488 489 // FIXME: Is loading timing OK? 490 LoadCoolSwitchSettings(); 491 492 if (!CoolSwitch) 493 return 0; 494 495 // Already in the loop. 496 if (switchdialog || Esc) return 0; 497 498 hwndActive = GetActiveWindow(); 499 // Nothing is active so exit. 500 if (!hwndActive) return 0; 501 502 if (lParam == VK_ESCAPE) 503 { 504 Esc = TRUE; 505 506 windowCount = 0; 507 EnumWindowsZOrder(EnumerateCallback, 0); 508 509 if (windowCount < 2) 510 return 0; 511 512 RotateTasks(GetAsyncKeyState(VK_SHIFT) < 0); 513 514 hwndActive = GetActiveWindow(); 515 516 if (hwndActive == NULL) 517 { 518 Esc = FALSE; 519 return 0; 520 } 521 } 522 523 // Capture current active window. 524 SetCapture( hwndActive ); 525 526 switch (lParam) 527 { 528 case VK_TAB: 529 if( !CreateSwitcherWindow(User32Instance) ) goto Exit; 530 if( !GetDialogFont() ) goto Exit; 531 if( !ProcessHotKey() ) goto Exit; 532 break; 533 534 case VK_ESCAPE: 535 break; 536 537 default: 538 goto Exit; 539 } 540 // Main message loop: 541 while (1) 542 { 543 for (;;) 544 { 545 if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE )) 546 { 547 if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break; 548 /* remove the message from the queue */ 549 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); 550 } 551 else 552 WaitMessage(); 553 } 554 555 switch (msg.message) 556 { 557 case WM_KEYUP: 558 { 559 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); 560 if (msg.wParam == VK_MENU) 561 { 562 CompleteSwitch(TRUE); 563 } 564 else if (msg.wParam == VK_RETURN) 565 { 566 CompleteSwitch(TRUE); 567 } 568 else if (msg.wParam == VK_ESCAPE) 569 { 570 TRACE("DoAppSwitch VK_ESCAPE 2\n"); 571 CompleteSwitch(FALSE); 572 } 573 goto Exit; //break; 574 } 575 576 case WM_SYSKEYDOWN: 577 { 578 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); 579 if (HIWORD(msg.lParam) & KF_ALTDOWN) 580 { 581 if ( msg.wParam == VK_TAB ) 582 { 583 if (Esc) break; 584 if (GetKeyState(VK_SHIFT) < 0) 585 { 586 selectedWindow = selectedWindow - 1; 587 if (selectedWindow < 0) 588 selectedWindow = windowCount - 1; 589 } 590 else 591 { 592 selectedWindow = (selectedWindow + 1)%windowCount; 593 } 594 InvalidateRect(switchdialog, NULL, TRUE); 595 } 596 else if ( msg.wParam == VK_ESCAPE ) 597 { 598 if (!Esc) break; 599 RotateTasks(GetKeyState(VK_SHIFT) < 0); 600 } 601 } 602 break; 603 } 604 605 case WM_LBUTTONUP: 606 PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); 607 ProcessMouseMessage(msg.message, msg.lParam); 608 goto Exit; 609 610 default: 611 if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE )) 612 { 613 TranslateMessage(&msg); 614 DispatchMessageW(&msg); 615 } 616 break; 617 } 618 } 619 Exit: 620 ReleaseCapture(); 621 if (switchdialog) DestroyWindow(switchdialog); 622 if (Esc) DestroyAppWindows(); 623 switchdialog = NULL; 624 selectedWindow = 0; 625 windowCount = 0; 626 Esc = FALSE; 627 return 0; 628 } 629 630 // 631 // Switch System Class Window Proc. 632 // 633 LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode ) 634 { 635 PWND pWnd; 636 PALTTABINFO ati; 637 pWnd = ValidateHwnd(hWnd); 638 if (pWnd) 639 { 640 if (!pWnd->fnid) 641 { 642 NtUserSetWindowFNID(hWnd, FNID_SWITCH); 643 } 644 } 645 646 switch (uMsg) 647 { 648 case WM_NCCREATE: 649 if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati)))) 650 return 0; 651 SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati ); 652 return TRUE; 653 654 case WM_SHOWWINDOW: 655 if (wParam) 656 { 657 PrepareWindow(); 658 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0); 659 ati->cbSize = sizeof(ALTTABINFO); 660 ati->cItems = nItems; 661 ati->cColumns = nCols; 662 ati->cRows = nRows; 663 if (nCols) 664 { 665 ati->iColFocus = (selectedWindow - nShift) % nCols; 666 ati->iRowFocus = (selectedWindow - nShift) / nCols; 667 } 668 else 669 { 670 ati->iColFocus = 0; 671 ati->iRowFocus = 0; 672 } 673 ati->cxItem = CX_ITEM_SPACE; 674 ati->cyItem = CY_ITEM_SPACE; 675 ati->ptStart = ptStart; 676 } 677 return 0; 678 679 case WM_MOUSEMOVE: 680 ProcessMouseMessage(uMsg, lParam); 681 return 0; 682 683 case WM_ACTIVATE: 684 if (wParam == WA_INACTIVE) 685 { 686 CompleteSwitch(FALSE); 687 } 688 return 0; 689 690 case WM_PAINT: 691 OnPaint(hWnd); 692 return 0; 693 694 case WM_DESTROY: 695 isOpen = FALSE; 696 ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0); 697 HeapFree( GetProcessHeap(), 0, ati ); 698 SetWindowLongPtrW( hWnd, 0, 0 ); 699 DestroyAppWindows(); 700 NtUserSetWindowFNID(hWnd, FNID_DESTROY); 701 return 0; 702 } 703 return DefWindowProcW(hWnd, uMsg, wParam, lParam); 704 } 705 706 LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 707 { 708 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE); 709 } 710 711 LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 712 { 713 return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE); 714 } 715