/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS system libraries * FILE: dll/win32/user32/controls/appswitch.c * PURPOSE: app switching functionality * PROGRAMMERS: Johannes Anderwald (janderwald@reactos.org) * David Quintana (gigaherz@gmail.com) */ #include #include WINE_DEFAULT_DEBUG_CHANNEL(user32); // limit the number of windows shown in the alt-tab window // 120 windows results in (12*40) by (10*40) pixels worth of icons. #define MAX_WINDOWS 120 // Global variables HWND switchdialog; HFONT dialogFont; int selectedWindow = 0; BOOL isOpen = FALSE; int fontHeight=0; WCHAR windowText[1024]; HWND windowList[MAX_WINDOWS]; HICON iconList[MAX_WINDOWS]; int windowCount = 0; int cxBorder, cyBorder; int nItems, nCols, nRows; int itemsW, itemsH; int totalW, totalH; int xOffset, yOffset; POINT pt; void ResizeAndCenter(HWND hwnd, int width, int height) { int screenwidth = GetSystemMetrics(SM_CXSCREEN); int screenheight = GetSystemMetrics(SM_CYSCREEN); pt.x = (screenwidth - width) / 2; pt.y = (screenheight - height) / 2; MoveWindow(hwnd, pt.x, pt.y, width, height, FALSE); } void MakeWindowActive(HWND hwnd) { WINDOWPLACEMENT wpl; wpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(hwnd, &wpl); TRACE("GetWindowPlacement wpl.showCmd %d\n",wpl.showCmd); if (wpl.showCmd == SW_SHOWMINIMIZED) ShowWindowAsync(hwnd, SW_RESTORE); BringWindowToTop(hwnd); // same as: SetWindowPos(hwnd,HWND_TOP,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE); ? SetForegroundWindow(hwnd); } void CompleteSwitch(BOOL doSwitch) { if (!isOpen) return; isOpen = FALSE; TRACE("[ATbot] CompleteSwitch Hiding Window.\n"); ShowWindow(switchdialog, SW_HIDE); if(doSwitch) { if(selectedWindow >= windowCount) return; // FIXME: workaround because reactos fails to activate the previous window correctly. //if(selectedWindow != 0) { HWND hwnd = windowList[selectedWindow]; GetWindowTextW(hwnd, windowText, 1023); TRACE("[ATbot] CompleteSwitch Switching to 0x%08x (%ls)\n", hwnd, windowText); MakeWindowActive(hwnd); } } windowCount = 0; } BOOL CALLBACK EnumerateCallback(HWND window, LPARAM lParam) { HICON hIcon; UNREFERENCED_PARAMETER(lParam); if (!IsWindowVisible(window)) return TRUE; GetClassNameW(window,windowText,4095); if ((wcscmp(L"Shell_TrayWnd",windowText)==0) || (wcscmp(L"Progman",windowText)==0) ) return TRUE; // First try to get the big icon assigned to the window hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_BIG, 0); if (!hIcon) { // If no icon is assigned, try to get the icon assigned to the windows' class hIcon = (HICON)GetClassLongPtrW(window, GCL_HICON); if (!hIcon) { // If we still don't have an icon, see if we can do with the small icon, // or a default application icon hIcon = (HICON)SendMessageW(window, WM_GETICON, ICON_SMALL2, 0); if (!hIcon) { // If all fails, give up and continue with the next window return TRUE; } } } windowList[windowCount] = window; iconList[windowCount] = CopyIcon(hIcon); windowCount++; // If we got to the max number of windows, // we won't be able to add any more if(windowCount == MAX_WINDOWS) return FALSE; return TRUE; } // Function mostly compatible with the normal EnumWindows, // except it lists in Z-Order and it doesn't ensure consistency // if a window is removed while enumerating void EnumWindowsZOrder(WNDENUMPROC callback, LPARAM lParam) { HWND next = GetTopWindow(NULL); while (next != NULL) { if(!callback(next, lParam)) break; next = GetWindow(next, GW_HWNDNEXT); } } void ProcessMouseMessage(UINT message, LPARAM lParam) { int xPos = LOWORD(lParam); int yPos = HIWORD(lParam); int xIndex = (xPos - xOffset)/40; int xOff = (xPos - xOffset)%40; int yIndex = (yPos - yOffset)/40; int yOff = (yPos - yOffset)%40; if(xOff > 32 || xIndex > nItems) return; if(yOff > 32 || yIndex > nRows) return; selectedWindow = (yIndex*nCols) + xIndex; if (message == WM_MOUSEMOVE) { InvalidateRect(switchdialog, NULL, TRUE); //RedrawWindow(switchdialog, NULL, NULL, 0); } else { selectedWindow = (yIndex*nCols) + xIndex; CompleteSwitch(TRUE); } } void OnPaint(HWND hWnd) { HDC dialogDC; PAINTSTRUCT paint; RECT cRC, textRC; int i; HBRUSH hBrush; HPEN hPen; HFONT dcFont; COLORREF cr; int nch = GetWindowTextW(windowList[selectedWindow], windowText, 1023); dialogDC = BeginPaint(hWnd, &paint); { GetClientRect(hWnd, &cRC); FillRect(dialogDC, &cRC, GetSysColorBrush(COLOR_MENU)); for(i=0; i< windowCount; i++) { HICON hIcon = iconList[i]; int xpos = xOffset + 40 * (i % nCols); int ypos = yOffset + 40 * (i / nCols); if (selectedWindow == i) { hBrush = GetSysColorBrush(COLOR_HIGHLIGHT); } else { hBrush = GetSysColorBrush(COLOR_MENU); } #if TRUE cr = GetSysColor(COLOR_BTNTEXT); // doesn't look right! >_< hPen = CreatePen(PS_DOT, 1, cr); SelectObject(dialogDC, hPen); SelectObject(dialogDC, hBrush); Rectangle(dialogDC, xpos-2, ypos-2, xpos+32+2, ypos+32+2); DeleteObject(hPen); // Must NOT destroy the system brush! #else RECT rc = { xpos-2, ypos-2, xpos+32+2, ypos+32+2 }; FillRect(dialogDC, &rc, hBrush); #endif DrawIcon(dialogDC, xpos, ypos, hIcon); } dcFont = SelectObject(dialogDC, dialogFont); SetTextColor(dialogDC, GetSysColor(COLOR_BTNTEXT)); SetBkColor(dialogDC, GetSysColor(COLOR_BTNFACE)); textRC.top = itemsH; textRC.left = 8; textRC.right = totalW - 8; textRC.bottom = totalH - 8; DrawTextW(dialogDC, windowText, nch, &textRC, DT_CENTER|DT_END_ELLIPSIS); SelectObject(dialogDC, dcFont); } EndPaint(hWnd, &paint); } DWORD CreateSwitcherWindow(HINSTANCE hInstance) { switchdialog = CreateWindowExW( WS_EX_TOPMOST|WS_EX_DLGMODALFRAME|WS_EX_TOOLWINDOW, WC_SWITCH, L"", WS_POPUP|WS_BORDER|WS_DISABLED, CW_USEDEFAULT, CW_USEDEFAULT, 400, 150, NULL, NULL, hInstance, NULL); if (!switchdialog) { TRACE("[ATbot] Task Switcher Window failed to create.\n"); return 0; } isOpen = FALSE; return 1; } DWORD GetDialogFont() { HDC tDC; TEXTMETRIC tm; dialogFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); tDC = GetDC(0); GetTextMetrics(tDC, &tm); fontHeight = tm.tmHeight; ReleaseDC(0, tDC); return 1; } void PrepareWindow() { cxBorder = GetSystemMetrics(SM_CXBORDER); cyBorder = GetSystemMetrics(SM_CYBORDER); nItems = windowCount; nCols = min(max(nItems,8),12); nRows = (nItems+nCols-1)/nCols; itemsW = nCols*32 + (nCols+1)*8; itemsH = nRows*32 + (nRows+1)*8; totalW = itemsW + 2*cxBorder + 4; totalH = itemsH + 2*cyBorder + fontHeight + 8; // give extra pixels for the window title xOffset = 8; yOffset = 8; if (nItems < nCols) { int w2 = nItems*32 + (nItems-1)*8; xOffset = (itemsW-w2)/2; } ResizeAndCenter(switchdialog, totalW, totalH); } void ProcessHotKey() { if (!isOpen) { windowCount=0; EnumWindowsZOrder(EnumerateCallback, 0); if (windowCount < 2) return; selectedWindow = 1; TRACE("[ATbot] HotKey Received. Opening window.\n"); ShowWindow(switchdialog, SW_SHOWNORMAL); MakeWindowActive(switchdialog); isOpen = TRUE; } else { TRACE("[ATbot] HotKey Received Rotating.\n"); selectedWindow = (selectedWindow + 1)%windowCount; InvalidateRect(switchdialog, NULL, TRUE); } } LRESULT WINAPI DoAppSwitch( WPARAM wParam, LPARAM lParam ) { HWND hwnd; MSG msg; BOOL Esc = FALSE; INT Count = 0; WCHAR Text[1024]; switchdialog = NULL; switch (lParam) { case VK_TAB: if( !CreateSwitcherWindow(User32Instance) ) return 0; if( !GetDialogFont() ) return 0; ProcessHotKey(); break; case VK_ESCAPE: windowCount = 0; Count = 0; EnumWindowsZOrder(EnumerateCallback, 0); if (windowCount < 2) return 0; if (wParam == SC_NEXTWINDOW) Count = 1; else { if (windowCount == 2) Count = 0; else Count = windowCount - 1; } TRACE("DoAppSwitch VK_ESCAPE 1 Count %d windowCount %d\n",Count,windowCount); hwnd = windowList[Count]; GetWindowTextW(hwnd, Text, 1023); TRACE("[ATbot] Switching to 0x%08x (%ls)\n", hwnd, Text); MakeWindowActive(hwnd); Esc = TRUE; break; default: return 0; } // Main message loop: while (1) { for (;;) { if (PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE )) { if (!CallMsgFilterW( &msg, MSGF_NEXTWINDOW )) break; /* remove the message from the queue */ PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); } else WaitMessage(); } switch (msg.message) { case WM_KEYUP: { PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); if (msg.wParam == VK_MENU) { CompleteSwitch(TRUE); } else if (msg.wParam == VK_RETURN) { CompleteSwitch(TRUE); } else if (msg.wParam == VK_ESCAPE) { TRACE("DoAppSwitch VK_ESCAPE 2\n"); CompleteSwitch(FALSE); } goto Exit; //break; } case WM_SYSKEYDOWN: { PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); if (HIWORD(msg.lParam) & KF_ALTDOWN) { INT Shift; if ( msg.wParam == VK_TAB ) { if (Esc) break; Shift = GetKeyState(VK_SHIFT) & 0x8000 ? SC_PREVWINDOW : SC_NEXTWINDOW; if (Shift == SC_NEXTWINDOW) { selectedWindow = (selectedWindow + 1)%windowCount; } else { selectedWindow = selectedWindow - 1; if (selectedWindow < 0) selectedWindow = windowCount - 1; } InvalidateRect(switchdialog, NULL, TRUE); } else if ( msg.wParam == VK_ESCAPE ) { if (!Esc) break; if (windowCount < 2) goto Exit; if (wParam == SC_NEXTWINDOW) { Count = (Count + 1)%windowCount; } else { Count--; if (Count < 0) Count = windowCount - 1; } hwnd = windowList[Count]; GetWindowTextW(hwnd, Text, 1023); MakeWindowActive(hwnd); } } break; } case WM_LBUTTONUP: PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE ); ProcessMouseMessage(msg.message, msg.lParam); goto Exit; default: if (PeekMessageW( &msg, 0, msg.message, msg.message, PM_REMOVE )) { TranslateMessage(&msg); DispatchMessageW(&msg); } break; } } Exit: if (switchdialog) DestroyWindow(switchdialog); switchdialog = NULL; selectedWindow = 0; windowCount = 0; return 0; } VOID DestroyAppWindows() { INT i; for (i=0; i< windowCount; i++) { HICON hIcon = iconList[i]; DestroyIcon(hIcon); } } // // Switch System Class Window Proc. // LRESULT WINAPI SwitchWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL unicode ) { PWND pWnd; PALTTABINFO ati; pWnd = ValidateHwnd(hWnd); if (pWnd) { if (!pWnd->fnid) { NtUserSetWindowFNID(hWnd, FNID_SWITCH); } } switch (uMsg) { case WM_NCCREATE: if (!(ati = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*ati)))) return 0; SetWindowLongPtrW( hWnd, 0, (LONG_PTR)ati ); return TRUE; case WM_SHOWWINDOW: if (wParam == TRUE) { PrepareWindow(); ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0); ati->cItems = nItems; ati->cxItem = ati->cyItem = 43; ati->cRows = nRows; ati->cColumns = nCols; } return 0; case WM_MOUSEMOVE: ProcessMouseMessage(uMsg, lParam); return 0; case WM_ACTIVATE: if (wParam == WA_INACTIVE) { CompleteSwitch(FALSE); } return 0; case WM_PAINT: OnPaint(hWnd); return 0; case WM_DESTROY: isOpen = FALSE; ati = (PALTTABINFO)GetWindowLongPtrW(hWnd, 0); HeapFree( GetProcessHeap(), 0, ati ); SetWindowLongPtrW( hWnd, 0, 0 ); DestroyAppWindows(); NtUserSetWindowFNID(hWnd, FNID_DESTROY); return 0; } return DefWindowProcW(hWnd, uMsg, wParam, lParam); } LRESULT WINAPI SwitchWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, FALSE); } LRESULT WINAPI SwitchWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { return SwitchWndProc_common(hWnd, uMsg, wParam, lParam, TRUE); }