mirror of
https://github.com/reactos/reactos.git
synced 2024-09-12 05:43:24 +00:00
564 lines
14 KiB
C
564 lines
14 KiB
C
/*
|
|
* 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 <user32.h>
|
|
|
|
#include <wine/debug.h>
|
|
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);
|
|
}
|