You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1231 lines
36 KiB
1231 lines
36 KiB
/***************************** Module Header ******************************\
|
|
* Module Name: ghost.c
|
|
*
|
|
* Copyright (c) 1985-1999, Microsoft Corporation
|
|
*
|
|
* Ghost support for unresponsive windows.
|
|
*
|
|
* History:
|
|
* 23-Apr-1999 vadimg created
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#ifdef HUNGAPP_GHOSTING
|
|
|
|
typedef struct tagGHOST *PGHOST;
|
|
typedef struct tagGHOST {
|
|
PGHOST pghostNext; // next structure in the linked list
|
|
PWND pwnd; // hung window we're trying to ghost
|
|
PWND pwndGhost; // ghost window created for this pwnd
|
|
HBITMAP hbm; // saved visual bits for the ghosted window
|
|
HRGN hrgn; // what visual bits are available to us
|
|
RECT rcClient; // client rect in window's coordinates
|
|
UINT fWarningText : 1; // whether the warning text has been added
|
|
UINT fSizedOrMoved : 1;
|
|
} GHOST, *PGHOST;
|
|
|
|
PGHOST gpghostFirst; // pointer to the start of the ghost list
|
|
PTHREADINFO gptiGhost; // pointer to ghost threadinfo
|
|
|
|
ULONG guGhostLinked;
|
|
ULONG guGhostUnlinked;
|
|
ULONG guGhostBmpCreated;
|
|
ULONG guGhostBmpFreed;
|
|
|
|
#define XY_MARGIN 10
|
|
#define MAXSTRING 256
|
|
|
|
#define GHOST_MAX 50
|
|
|
|
/***************************************************************************\
|
|
* _DisableProcessWindowsGhosting
|
|
*
|
|
* Diables ghosting windows for the calling process.
|
|
* History:
|
|
* 31-May-01 MSadek Created.
|
|
\***************************************************************************/
|
|
VOID _DisableProcessWindowsGhosting(
|
|
VOID)
|
|
{
|
|
PpiCurrent()->W32PF_Flags |= W32PF_DISABLEWINDOWSGHOSTING;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GhostFromGhostPwnd
|
|
*
|
|
* Find the ghost structure for this ghost window.
|
|
\***************************************************************************/
|
|
PGHOST GhostFromGhostPwnd(
|
|
PWND pwndGhost)
|
|
{
|
|
PGHOST pghost;
|
|
|
|
for (pghost = gpghostFirst; pghost != NULL; pghost = pghost->pghostNext) {
|
|
if (pghost->pwndGhost == pwndGhost) {
|
|
return pghost;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GhostFromPwnd
|
|
*
|
|
\***************************************************************************/
|
|
PGHOST GhostFromPwnd(
|
|
PWND pwnd)
|
|
{
|
|
PGHOST pghost;
|
|
|
|
for (pghost = gpghostFirst; pghost != NULL; pghost = pghost->pghostNext) {
|
|
if (pghost->pwnd == pwnd) {
|
|
return pghost;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* FindGhost
|
|
*
|
|
* Find a ghost that corresponds to this hung window.
|
|
\***************************************************************************/
|
|
PWND FindGhost(
|
|
PWND pwnd)
|
|
{
|
|
PGHOST pghost = GhostFromPwnd(pwnd);
|
|
|
|
if (pghost != NULL) {
|
|
return pghost->pwndGhost;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GhostSizedOrMoved
|
|
*
|
|
* Returns true if the ghost window corresponding to a window was sized or moved
|
|
* through its life time.
|
|
\***************************************************************************/
|
|
BOOL GhostSizedOrMoved(
|
|
PWND pwnd)
|
|
{
|
|
PGHOST pghost = GhostFromPwnd(pwnd);
|
|
|
|
if (pghost != NULL) {
|
|
return pghost->fSizedOrMoved;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UnlinkAndFreeGhost
|
|
*
|
|
* This function unlinks a ghost element from the list and free its allocated
|
|
* memory.
|
|
\***************************************************************************/
|
|
_inline VOID UnlinkAndFreeGhost(
|
|
PGHOST* ppghost,
|
|
PGHOST pghost)
|
|
{
|
|
UserAssert(pghost->hbm == NULL);
|
|
|
|
*ppghost = pghost->pghostNext;
|
|
UserFreePool(pghost);
|
|
guGhostUnlinked++;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GetWindowIcon
|
|
*
|
|
* Get a window icon. If asked try the large icon first, then the small icon,
|
|
* then the windows logo icon.
|
|
\***************************************************************************/
|
|
PICON GetWindowIcon(
|
|
PWND pwnd,
|
|
BOOL fBigIcon)
|
|
{
|
|
HICON hicon;
|
|
PICON picon = NULL;
|
|
|
|
if (fBigIcon) {
|
|
hicon = (HICON)_GetProp(pwnd, MAKEINTATOM(gpsi->atomIconProp), TRUE);
|
|
if (hicon) {
|
|
picon = (PICON)HMValidateHandleNoRip(hicon, TYPE_CURSOR);
|
|
}
|
|
|
|
if (picon == NULL) {
|
|
picon = pwnd->pcls->spicn;
|
|
}
|
|
}
|
|
|
|
if (picon == NULL) {
|
|
hicon = (HICON)_GetProp(pwnd, MAKEINTATOM(gpsi->atomIconSmProp), TRUE);
|
|
|
|
if (hicon != NULL) {
|
|
picon = (PICON)HMValidateHandleNoRip(hicon, TYPE_CURSOR);
|
|
}
|
|
|
|
if (picon == NULL) {
|
|
picon = pwnd->pcls->spicnSm;
|
|
}
|
|
}
|
|
|
|
return picon;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* AddGhost
|
|
*
|
|
* Add a new ghost structure for a hung window.
|
|
\***************************************************************************/
|
|
BOOL AddGhost(
|
|
PWND pwnd)
|
|
{
|
|
PGHOST pghost;
|
|
CheckCritIn();
|
|
|
|
/*
|
|
* Need to limit the maximum number of ghost windows created as not to
|
|
* result into thread starvation.
|
|
*/
|
|
if (guGhostLinked - guGhostUnlinked == GHOST_MAX) {
|
|
return FALSE;
|
|
}
|
|
|
|
if ((pghost = (PGHOST)UserAllocPoolZInit(sizeof(GHOST), TAG_GHOST)) == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
pghost->pghostNext = gpghostFirst;
|
|
gpghostFirst = pghost;
|
|
|
|
pghost->pwnd = pwnd;
|
|
|
|
/*
|
|
* When pwndGhost is NULL, the ghost thread will try to create a ghost
|
|
* window for this hung window.
|
|
*/
|
|
KeSetEvent(gpEventScanGhosts, EVENT_INCREMENT, FALSE);
|
|
guGhostLinked++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL AddOwnedWindowToGhostList(
|
|
PWND pwndRoot,
|
|
PWND pwndOrg)
|
|
{
|
|
PWND pwnd = NULL;
|
|
|
|
while (pwnd = NextOwnedWindow(pwnd, pwndRoot, pwndRoot->spwndParent)) {
|
|
if (!AddOwnedWindowToGhostList(pwnd, pwndOrg)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* We need to add the bottom window on the chain first to the ghost
|
|
* list because we scan the list from the head thus, ensure that the
|
|
* owned window is already created at the time we create the ownee
|
|
* ghost.
|
|
*/
|
|
if (GhostFromPwnd(pwnd) == NULL) {
|
|
if (!AddGhost(pwnd)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (GETPTI(pwndOrg) != GETPTI(pwndRoot)) {
|
|
RIPMSGF4(RIP_WARNING,
|
|
"Cross thread ghosting pwnd: 0x%p pti 0x%p, pwndRoot: 0x%p pti 0x%p",
|
|
pwndOrg,
|
|
GETPTI(pwndOrg),
|
|
pwndRoot,
|
|
GETPTI(pwndRoot));
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL AddGhostOwnersAndOwnees(
|
|
PWND pwnd)
|
|
{
|
|
PWND pwndRoot = pwnd;
|
|
|
|
/*
|
|
* Get the topmost owner window in the chain.
|
|
*/
|
|
while(pwndRoot->spwndOwner != NULL) {
|
|
pwndRoot = pwndRoot->spwndOwner;
|
|
}
|
|
|
|
/*
|
|
* Now starting form that window, walk the whole ownee tree.
|
|
*/
|
|
if (!AddOwnedWindowToGhostList(pwndRoot, pwnd)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* For the topmost window (or the only single window if there is no Owner / Ownee
|
|
* relationship at all, add the window to the ghost list
|
|
*/
|
|
if (GhostFromPwnd(pwndRoot) == NULL) {
|
|
if (!AddGhost(pwndRoot)) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (GETPTI(pwnd) != GETPTI(pwndRoot)) {
|
|
RIPMSGF4(RIP_WARNING,
|
|
"Cross thread ghosting pwnd: 0x%p pti 0x%p, pwndRoot: 0x%p pti 0x%p",
|
|
pwnd,
|
|
GETPTI(pwnd),
|
|
pwndRoot,
|
|
GETPTI(pwndRoot));
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if GHOST_AGGRESSIVE
|
|
|
|
/***************************************************************************\
|
|
* DimSavedBits
|
|
*
|
|
\***************************************************************************/
|
|
VOID DimSavedBits(
|
|
PGHOST pghost)
|
|
{
|
|
HBITMAP hbm, hbmOld, hbmOld2;
|
|
LONG cx, cy;
|
|
RECT rc;
|
|
BLENDFUNCTION blend;
|
|
|
|
if (pghost->hbm == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (gpDispInfo->fAnyPalette) {
|
|
return;
|
|
}
|
|
|
|
cx = pghost->rcClient.right - pghost->rcClient.left;
|
|
cy = pghost->rcClient.bottom - pghost->rcClient.top;
|
|
|
|
hbm = GreCreateCompatibleBitmap(gpDispInfo->hdcScreen, cx, cy);
|
|
if (hbm == NULL) {
|
|
return;
|
|
}
|
|
|
|
hbmOld = GreSelectBitmap(ghdcMem, hbm);
|
|
hbmOld2 = GreSelectBitmap(ghdcMem2, pghost->hbm);
|
|
|
|
rc.left = rc.top = 0;
|
|
rc.right = cx;
|
|
rc.bottom = cy;
|
|
FillRect(ghdcMem, &rc, SYSHBR(MENU));
|
|
|
|
blend.BlendOp = AC_SRC_OVER;
|
|
blend.BlendFlags = AC_MIRRORBITMAP;
|
|
blend.AlphaFormat = 0;
|
|
blend.SourceConstantAlpha = 150;
|
|
GreAlphaBlend(ghdcMem, 0, 0, cx, cy, ghdcMem2, 0, 0, cx, cy, blend, NULL);
|
|
|
|
GreSelectBitmap(ghdcMem, hbmOld);
|
|
GreSelectBitmap(ghdcMem2, hbmOld2);
|
|
|
|
GreDeleteObject(pghost->hbm);
|
|
pghost->hbm = hbm;
|
|
}
|
|
|
|
#endif
|
|
|
|
/***************************************************************************\
|
|
* SaveVisualBits
|
|
*
|
|
\***************************************************************************/
|
|
VOID SaveVisualBits(
|
|
PGHOST pghost)
|
|
{
|
|
BOOL fSaveBits;
|
|
PWND pwnd;
|
|
HBITMAP hbmOld;
|
|
int cx, cy;
|
|
RECT rcT;
|
|
HDC hdc;
|
|
|
|
fSaveBits = FALSE;
|
|
pwnd = pghost->pwnd;
|
|
|
|
/*
|
|
* Nothing to save if the window is completely invalid.
|
|
*/
|
|
if (pwnd->hrgnUpdate != HRGN_FULL) {
|
|
|
|
CalcVisRgn(&pghost->hrgn, pwnd, pwnd, DCX_CLIPSIBLINGS);
|
|
|
|
/*
|
|
* Only can save bits if the window is not completely obscured and
|
|
* either there is no invalid bits or if there are bits left over
|
|
* after we subtract the invalid bits from the visible area.
|
|
*/
|
|
if (pghost->hrgn != NULL &&
|
|
GreGetRgnBox(pghost->hrgn, &rcT) != NULLREGION) {
|
|
|
|
if (pwnd->hrgnUpdate == NULL) {
|
|
fSaveBits = TRUE;
|
|
} else {
|
|
|
|
/*
|
|
* We'll use the bounding box of the invalid region of the
|
|
* ghost window as an approximation of the total invalid
|
|
* region, this way we won't have to go through all of the
|
|
* children.
|
|
*/
|
|
GreGetRgnBox(pwnd->hrgnUpdate, &rcT);
|
|
SetRectRgnIndirect(ghrgnGDC, &rcT);
|
|
|
|
if (SubtractRgn(pghost->hrgn, pghost->hrgn, ghrgnGDC) != NULLREGION) {
|
|
fSaveBits = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now try to save the bits.
|
|
*/
|
|
if (fSaveBits) {
|
|
UserAssert(pghost->hrgn != NULL);
|
|
|
|
cx = pwnd->rcClient.right - pwnd->rcClient.left;
|
|
cy = pwnd->rcClient.bottom - pwnd->rcClient.top;
|
|
|
|
if (pghost->hbm != NULL) {
|
|
FRE_RIPMSG0(RIP_ERROR, "SaveVisaulBits: overriding pghost->hbm");
|
|
}
|
|
|
|
/*
|
|
* Use NOVIDEOMEMORY here, because for the blend we'll have to be
|
|
* reading from this bitmap and reading from video memory is slow
|
|
* when the alpha isn't done by the graphics card but by GDI.
|
|
*/
|
|
pghost->hbm = GreCreateCompatibleBitmap(gpDispInfo->hdcScreen, cx, cy | CCB_NOVIDEOMEMORY);
|
|
guGhostBmpCreated++;
|
|
|
|
if (pghost->hbm != NULL) {
|
|
int dx, dy;
|
|
|
|
dx = pghost->pwnd->rcClient.left - pghost->pwndGhost->rcClient.left;
|
|
dy = pghost->pwnd->rcClient.top - pghost->pwndGhost->rcClient.top;
|
|
|
|
/*
|
|
* Get the visual bits rectangle in ghost client rect origin.
|
|
*/
|
|
pghost->rcClient.left = dx;
|
|
pghost->rcClient.top = dy;
|
|
pghost->rcClient.right = dx + cx;
|
|
pghost->rcClient.bottom = dy + cy;
|
|
|
|
/*
|
|
* Make the region originate in the ghost client rect origin.
|
|
*/
|
|
GreOffsetRgn(pghost->hrgn,
|
|
-pwnd->rcClient.left + dx,
|
|
-pwnd->rcClient.top + dy);
|
|
|
|
hbmOld = GreSelectBitmap(ghdcMem, pghost->hbm);
|
|
hdc = _GetDC(pghost->pwnd);
|
|
|
|
GreBitBlt(ghdcMem, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY, 0);
|
|
|
|
_ReleaseDC(hdc);
|
|
GreSelectBitmap(ghdcMem, hbmOld);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clean up the region if couldn't save the visual bits successfully.
|
|
*/
|
|
if (pghost->hbm == NULL && pghost->hrgn != NULL) {
|
|
GreDeleteObject(pghost->hrgn);
|
|
pghost->hrgn = NULL;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxAddWarningText
|
|
*
|
|
\***************************************************************************/
|
|
VOID xxxAddWarningText(
|
|
PWND pwnd)
|
|
{
|
|
WCHAR szText[CCHTITLEMAX];
|
|
UINT cch, cchNR;
|
|
LARGE_STRING strName;
|
|
WCHAR szNR[MAXSTRING];
|
|
|
|
ServerLoadString(hModuleWin, STR_NOT_RESPONDING, szNR, ARRAY_SIZE(szNR));
|
|
|
|
/*
|
|
* Add "Not responding" to the end of the title text.
|
|
*/
|
|
cch = TextCopy(&pwnd->strName, szText, CCHTITLEMAX);
|
|
cchNR = wcslen(szNR);
|
|
cch = min(CCHTITLEMAX - cchNR - 1, cch);
|
|
wcscpy(szText + cch, szNR);
|
|
strName.bAnsi = FALSE;
|
|
strName.Buffer = szText;
|
|
strName.Length = (USHORT)((cch + cchNR) * sizeof(WCHAR));
|
|
strName.MaximumLength = strName.Length + sizeof(UNICODE_NULL);
|
|
|
|
xxxDefWindowProc(pwnd, WM_SETTEXT, 0, (LPARAM)&strName);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxCreateGhostWindow
|
|
*
|
|
\***************************************************************************/
|
|
BOOL xxxCreateGhostWindow(
|
|
PGHOST pghost)
|
|
{
|
|
PWND pwnd;
|
|
PWND pwndGhost;
|
|
PWND pwndOwner = NULL;
|
|
PGHOST pghostOwner = NULL;
|
|
PTHREADINFO pti;
|
|
HWND hwnd, hwndGhost;
|
|
TL tlpwndT1, tlpwndT2, tlpwndT3, tlpwndT4, tlpwndT5;
|
|
PWND pwndPrev;
|
|
DWORD dwFlags, style, ExStyle;
|
|
PICON picon;
|
|
LARGE_UNICODE_STRING str;
|
|
UINT cbAlloc;
|
|
BOOL fHasOwner = FALSE;
|
|
|
|
if (gbCleanupInitiated) {
|
|
FRE_RIPMSG0(RIP_ERROR, "Trying to create a ghost window while shutdown is in progress");
|
|
return FALSE;
|
|
}
|
|
pwnd = pghost->pwnd;
|
|
|
|
cbAlloc = pwnd->strName.Length + sizeof(WCHAR);
|
|
str.Buffer = UserAllocPoolWithQuota(cbAlloc, TAG_GHOST);
|
|
|
|
if (str.Buffer == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
str.MaximumLength = cbAlloc;
|
|
str.Length = pwnd->strName.Length;
|
|
str.bAnsi = FALSE;
|
|
|
|
RtlCopyMemory(str.Buffer, pwnd->strName.Buffer, str.Length);
|
|
|
|
str.Buffer[str.Length / sizeof(WCHAR)] = 0;
|
|
|
|
ThreadLock(pwnd, &tlpwndT1);
|
|
ThreadLockPool(ptiCurrent, str.Buffer, &tlpwndT2);
|
|
|
|
if (pwnd->spwndOwner && ((pghostOwner = GhostFromPwnd(pwnd->spwndOwner)) != NULL) &&
|
|
((pwndOwner = pghostOwner->pwndGhost)) != NULL) {
|
|
fHasOwner = TRUE;
|
|
ThreadLock(pwndOwner, &tlpwndT3);
|
|
}
|
|
|
|
/*
|
|
* Create the ghost window invisible and disallow it to be
|
|
* maximized since it would be kind of pointless...
|
|
* We don't remove the WS_MAXIMIZEBOX box here as
|
|
* GetMonitorMaxArea() checks on WFMAXBOX to judge
|
|
* if the window should be maximized to the full screen
|
|
* area or to the working area (and it is being called during window creation).
|
|
* See bug# 320325
|
|
*/
|
|
ExStyle = (pwnd->ExStyle & ~(WS_EX_LAYERED | WS_EX_COMPOSITED)) & WS_EX_ALLVALID;
|
|
style = pwnd->style & ~(WS_VISIBLE | WS_DISABLED);
|
|
|
|
pwndGhost = xxxNVCreateWindowEx(ExStyle, (PLARGE_STRING)gatomGhost,
|
|
(PLARGE_STRING)&str, style,
|
|
pwnd->rcWindow.left, pwnd->rcWindow.top,
|
|
pwnd->rcWindow.right - pwnd->rcWindow.left,
|
|
pwnd->rcWindow.bottom - pwnd->rcWindow.top,
|
|
pwndOwner, NULL, hModuleWin, NULL, WINVER);
|
|
|
|
if (pwndGhost == NULL || (pghost = GhostFromPwnd(pwnd)) == NULL) {
|
|
if (fHasOwner) {
|
|
ThreadUnlock(&tlpwndT3);
|
|
}
|
|
ThreadUnlockAndFreePool(ptiCurrent, &tlpwndT2);
|
|
ThreadUnlock(&tlpwndT1);
|
|
return FALSE;
|
|
}
|
|
|
|
pghost->pwndGhost = pwndGhost;
|
|
|
|
/*
|
|
* Try to get large and small icons for the hung window. Since
|
|
* we store the handles, it should be OK if these icons
|
|
* somehow go away while the ghost window still exists.
|
|
*/
|
|
if ((picon = GetWindowIcon(pwnd, TRUE)) != NULL) {
|
|
InternalSetProp(pwndGhost, MAKEINTATOM(gpsi->atomIconProp),
|
|
(HANDLE)PtoHq(picon), PROPF_INTERNAL | PROPF_NOPOOL);
|
|
}
|
|
if ((picon = GetWindowIcon(pwnd, FALSE)) != NULL) {
|
|
InternalSetProp(pwndGhost, MAKEINTATOM(gpsi->atomIconSmProp),
|
|
(HANDLE)PtoHq(picon), PROPF_INTERNAL | PROPF_NOPOOL);
|
|
}
|
|
|
|
/*
|
|
* Now remove WFMAXBOX before painting the window.
|
|
*/
|
|
ClrWF(pwndGhost, WFMAXBOX);
|
|
SaveVisualBits(pghost);
|
|
|
|
#if GHOST_AGGRESSIVE
|
|
DimSavedBits(pghost);
|
|
#endif
|
|
|
|
/*
|
|
* If the hung window is the active foreground window, allow
|
|
* the activation to bring the ghost window to the foreground.
|
|
*/
|
|
dwFlags = SWP_NOSIZE | SWP_NOMOVE;
|
|
|
|
if (TestWF(pwnd, WFVISIBLE)) {
|
|
dwFlags |= SWP_SHOWWINDOW;
|
|
SetWF(pwnd, WEFGHOSTMAKEVISIBLE);
|
|
}
|
|
|
|
pti = GETPTI(pwnd);
|
|
|
|
if (pti->pq == gpqForeground && pti->pq->spwndActive == pwnd) {
|
|
PtiCurrent()->TIF_flags |= TIF_ALLOWFOREGROUNDACTIVATE;
|
|
} else {
|
|
dwFlags |= SWP_NOACTIVATE;
|
|
}
|
|
|
|
/*
|
|
* We will zorder the ghost window right above the hung window.
|
|
*/
|
|
pwndPrev = _GetWindow(pwnd, GW_HWNDPREV);
|
|
if (pwndPrev == pwndGhost) {
|
|
dwFlags |= SWP_NOZORDER;
|
|
pwndPrev = NULL;
|
|
}
|
|
|
|
ThreadLock(pwndGhost, &tlpwndT4);
|
|
ThreadLock(pwndPrev, &tlpwndT5);
|
|
|
|
/*
|
|
* Make the shell remove the hung window from the taskbar. From
|
|
* now on users will be dealing with the system menu on the
|
|
* ghost window.
|
|
*/
|
|
hwnd = HWq(pwnd);
|
|
hwndGhost = HWq(pwndGhost);
|
|
PostShellHookMessages(HSHELL_WINDOWREPLACING, (LPARAM)hwndGhost);
|
|
PostShellHookMessages(HSHELL_WINDOWREPLACED, (LPARAM)hwnd);
|
|
xxxCallHook(HSHELL_WINDOWREPLACED, (WPARAM)hwnd, (LPARAM)hwndGhost, WH_SHELL);
|
|
|
|
xxxSetWindowPos(pwndGhost, pwndPrev, 0, 0, 0, 0, dwFlags);
|
|
|
|
/*
|
|
* Clear the visible bit on the hung window now and post our
|
|
* queue message which will figure out when it wakes up.
|
|
*/
|
|
if (TestWF(pwnd, WEFGHOSTMAKEVISIBLE)) {
|
|
SetVisible(pwnd, SV_UNSET);
|
|
}
|
|
pti = GETPTI(pwnd);
|
|
PostEventMessage(pti, pti->pq, QEVENT_HUNGTHREAD, pwnd, 0, 0, 0);
|
|
|
|
zzzWindowEvent(EVENT_OBJECT_HIDE, pwnd, OBJID_WINDOW, INDEXID_CONTAINER, WEF_USEPWNDTHREAD);
|
|
|
|
/*
|
|
* If the end user clicked and held on the hung window, fake
|
|
* this mouse click to the ghost window. This also ensures that
|
|
* the attempted dragging operation will not be interrupted.
|
|
*/
|
|
if (gspwndMouseOwner == pwnd) {
|
|
Lock(&gspwndMouseOwner, pwndGhost);
|
|
|
|
PostInputMessage(GETPTI(pwndGhost)->pq, pwndGhost, WM_LBUTTONDOWN,
|
|
0, MAKELONG((SHORT)gptCursorAsync.x, (SHORT)gptCursorAsync.y),
|
|
0, 0);
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndT5);
|
|
ThreadUnlock(&tlpwndT4);
|
|
if (fHasOwner) {
|
|
ThreadUnlock(&tlpwndT3);
|
|
}
|
|
ThreadUnlockAndFreePool(ptiCurrent, &tlpwndT2);
|
|
ThreadUnlock(&tlpwndT1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* CleanupGhost
|
|
*
|
|
* Cleans up an ghost structure entry
|
|
* Handles the case when the ghost thread got destroyed during callback
|
|
* History:
|
|
* 29-Nov-00 MSadek Created.
|
|
\***************************************************************************/
|
|
PWND CleanupGhost(
|
|
PGHOST *ppghost,
|
|
PGHOST pghost)
|
|
{
|
|
PWND pwndGhost;
|
|
|
|
if (pghost->hrgn != NULL) {
|
|
GreDeleteObject(pghost->hrgn);
|
|
}
|
|
|
|
if (pghost->hbm != NULL) {
|
|
GreDeleteObject(pghost->hbm);
|
|
guGhostBmpFreed++;
|
|
pghost->hbm = NULL;
|
|
}
|
|
|
|
/*
|
|
* We used the icon handles owned by the ghosted window, so
|
|
* we will only remove the properties without actually
|
|
* destroying the icons, as it would happen in DestroyWindow.
|
|
*/
|
|
pwndGhost = pghost->pwndGhost;
|
|
if (pwndGhost != NULL) {
|
|
InternalRemoveProp(pwndGhost,
|
|
MAKEINTATOM(gpsi->atomIconProp), PROPF_INTERNAL);
|
|
InternalRemoveProp(pwndGhost,
|
|
MAKEINTATOM(gpsi->atomIconSmProp), PROPF_INTERNAL);
|
|
}
|
|
UnlinkAndFreeGhost(ppghost, pghost);
|
|
|
|
return pwndGhost;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* ResetGhostThreadInfo
|
|
*
|
|
* Does a celanup for the ghost windows global linked list.
|
|
* Add a comment reading that we need to clean up the list, if we die unexpectedly
|
|
* because we don't know if a ghost thread will got created again.
|
|
* History:
|
|
* 12-Oct-00 MSadek Created.
|
|
\***************************************************************************/
|
|
VOID ResetGhostThreadInfo(
|
|
PTHREADINFO pti)
|
|
{
|
|
PGHOST* ppghost;
|
|
PGHOST pghost;
|
|
|
|
UNREFERENCED_PARAMETER(pti);
|
|
|
|
ppghost = &gpghostFirst;
|
|
|
|
if (gpghostFirst != NULL) {
|
|
RIPMSGF0(RIP_WARNING,
|
|
"Ghost thread died while the ghost list is not empty");
|
|
}
|
|
|
|
while (*ppghost != NULL) {
|
|
pghost = *ppghost;
|
|
CleanupGhost(ppghost, pghost);
|
|
}
|
|
|
|
UserAssert(pti == gptiGhost);
|
|
gptiGhost = NULL;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* ScanGhosts
|
|
*
|
|
* This is our core function that will scan through the ghost list. It must
|
|
* always be called in the context of the ghost thread which assures that all
|
|
* creation and destruction of ghost windows happen in the context of that
|
|
* thread. When in the ghost structure
|
|
*
|
|
* pwnd is NULL - the hung window has been destroyed or the thread it's on
|
|
* woke up and so we need to destroy the ghost window.
|
|
*
|
|
* pwndGhost is NULL - the thread that pwnd is on is hung and so create the
|
|
* ghost window for it.
|
|
*
|
|
* 6-2-1999 vadimg created
|
|
\***************************************************************************/
|
|
BOOL xxxScanGhosts(
|
|
VOID)
|
|
{
|
|
PGHOST* ppghost;
|
|
PGHOST pghost;
|
|
PWND pwndTemp;
|
|
ULONG uGhostUnlinked;
|
|
|
|
CheckCritIn();
|
|
ppghost = &gpghostFirst;
|
|
|
|
while (*ppghost != NULL) {
|
|
|
|
pghost = *ppghost;
|
|
|
|
/*
|
|
* pwnd is NULL means we need to destroy the ghost window. Note, we
|
|
* need to remove the ghost from the list first to make sure that
|
|
* xxxFreeWindow can't find the ghost in the list and try to destroy
|
|
* the ghost window again causing an infinite loop.
|
|
*/
|
|
if (pghost->pwnd == NULL) {
|
|
pwndTemp = CleanupGhost(ppghost, pghost);
|
|
|
|
if (pwndTemp != NULL) {
|
|
uGhostUnlinked = guGhostUnlinked;
|
|
xxxDestroyWindow(pwndTemp);
|
|
|
|
/*
|
|
* If we have called back, the pointers might be invalid.
|
|
* Let's start the search again.
|
|
*/
|
|
if (uGhostUnlinked != guGhostUnlinked) {
|
|
ppghost = &gpghostFirst;
|
|
continue;
|
|
}
|
|
}
|
|
} else if (pghost->pwndGhost == NULL) {
|
|
HWND hwnd;
|
|
PGHOST pghostTemp = pghost;
|
|
|
|
pwndTemp = pghost->pwnd;
|
|
hwnd = PtoHq(pwndTemp);
|
|
uGhostUnlinked = guGhostUnlinked;
|
|
if (!xxxCreateGhostWindow(pghost)) {
|
|
/*
|
|
* If window creation failed, clean up by removing the struct
|
|
* from the list altogether.
|
|
*/
|
|
if (RevalidateCatHwnd(hwnd) && (pghost = GhostFromPwnd(pwndTemp))) {
|
|
UserAssert(pghost->pwndGhost == NULL);
|
|
RemoveGhost(pwndTemp);
|
|
}
|
|
} else {
|
|
#if DBG
|
|
if (RevalidateCatHwnd(hwnd) && (pghost = GhostFromPwnd(pwndTemp)) && (pghost == pghostTemp)) {
|
|
UserAssert(pghost->pwndGhost != NULL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* If we have called back, the pointers might be invalid. Let's
|
|
* start the search again.
|
|
*/
|
|
if (uGhostUnlinked != guGhostUnlinked) {
|
|
ppghost = &gpghostFirst;
|
|
continue;
|
|
}
|
|
} else {
|
|
ppghost = &pghost->pghostNext;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If there are no more ghosts left, cleanup and terminate this
|
|
* thread. by returning FALSE.
|
|
*/
|
|
if (gpghostFirst == NULL) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* GhostThread
|
|
*
|
|
* The thread that will service hung windows. It's created on demand and is
|
|
* terminated when the last ghost window is destroyed.
|
|
\***************************************************************************/
|
|
VOID GhostThread(
|
|
PDESKTOP pdesk)
|
|
{
|
|
NTSTATUS status;
|
|
DWORD dwResult;
|
|
MSG msg;
|
|
PKEVENT rgEvents[2];
|
|
BOOL fLoop = TRUE;
|
|
BOOL fCSRSSThread = ISCSRSS();
|
|
TL tlGhost;
|
|
|
|
if (fCSRSSThread) {
|
|
/*
|
|
* Make this a GUI thread.
|
|
*/
|
|
status = InitSystemThread(NULL);
|
|
}
|
|
|
|
EnterCrit();
|
|
|
|
/*
|
|
* Don't allow multiple ghost threads to be created.
|
|
*/
|
|
if (NULL != gptiGhost) {
|
|
LeaveCrit();
|
|
return;
|
|
}
|
|
gptiGhost = PtiCurrent();
|
|
ThreadLockPoolCleanup(gptiGhost, gptiGhost, &tlGhost, ResetGhostThreadInfo);
|
|
|
|
/*
|
|
* Try to assign this thread to the desktop. Any ghost windows can be
|
|
* created only on that desktop.
|
|
*/
|
|
if (fCSRSSThread) {
|
|
if (!NT_SUCCESS(status) || !xxxSetThreadDesktop(NULL, pdesk)) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
gptiGhost->pwinsta = pdesk->rpwinstaParent;
|
|
|
|
rgEvents[0] = gpEventScanGhosts;
|
|
|
|
/*
|
|
* Scan the list, since gptiGhost was NULL up to now and thus no posted
|
|
* messages could reach us.
|
|
*/
|
|
|
|
while (fLoop) {
|
|
|
|
/*
|
|
* Wait for any message sent or posted to this queue, while calling
|
|
* ProcessDeviceChanges whenever the mouse change event (pkeHidChange)
|
|
* is set.
|
|
*/
|
|
dwResult = xxxMsgWaitForMultipleObjects(1, rgEvents, NULL, NULL);
|
|
|
|
/*
|
|
* result tells us the type of event we have:
|
|
* a message or a signalled handle
|
|
*
|
|
* if there are one or more messages in the queue ...
|
|
*/
|
|
if (dwResult == WAIT_OBJECT_0) {
|
|
fLoop = xxxScanGhosts();
|
|
} else if (dwResult == STATUS_USER_APC){
|
|
goto Cleanup;
|
|
} else {
|
|
UserAssert(dwResult == WAIT_OBJECT_0 + 1);
|
|
|
|
while (xxxPeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
xxxDispatchMessage(&msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
ThreadUnlockPoolCleanup(gptiGhost, &tlGhost);
|
|
ResetGhostThreadInfo(PtiCurrent());
|
|
LeaveCrit();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxCreateGhost
|
|
*
|
|
* This function will create a ghost thread when needed and add a request
|
|
* to create a ghost to the ghost list.
|
|
\***************************************************************************/
|
|
BOOL xxxCreateGhost(
|
|
PWND pwnd)
|
|
{
|
|
USER_API_MSG m;
|
|
NTSTATUS Status;
|
|
PDESKTOP pdesk;
|
|
BOOL bRemoteThread = FALSE;
|
|
HANDLE UniqueProcessId = 0;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Bail out early for winlogon windows.
|
|
*/
|
|
pdesk = pwnd->head.rpdesk;
|
|
if (pdesk == grpdeskLogon) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* We can only service windows on the same desktop.
|
|
*/
|
|
if (gptiGhost != NULL && gptiGhost->rpdesk != pdesk) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Don't try to ghost windows from the ghost thread itself.
|
|
*/
|
|
if (GETPTI(pwnd) == gptiGhost) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Not much we can do if this hung window doesn't have a caption.
|
|
*/
|
|
if (TestWF(pwnd, WFCAPTION) != LOBYTE(WFCAPTION)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Try to create a ghost thread. Note that the event can have a value
|
|
* though the thread is NULL. This could happen if the thread died
|
|
* before making it to the kernel.
|
|
*/
|
|
if (gptiGhost == NULL) {
|
|
PPROCESSINFO ppi, ppiShellProcess = NULL;
|
|
|
|
if (gpEventScanGhosts == NULL) {
|
|
gpEventScanGhosts = CreateKernelEvent(SynchronizationEvent, FALSE);
|
|
if (gpEventScanGhosts == NULL) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
UserAssert (ISCSRSS());
|
|
|
|
ppi = GETPTI(pwnd)->ppi;
|
|
if (ppi->rpdeskStartup && ppi->rpdeskStartup->pDeskInfo) {
|
|
ppiShellProcess = ppi->rpdeskStartup->pDeskInfo->ppiShellProcess;
|
|
}
|
|
if (ppiShellProcess && ppiShellProcess->Process != gpepCSRSS) {
|
|
bRemoteThread = TRUE;
|
|
|
|
UniqueProcessId = PsGetProcessId(ppiShellProcess->Process);
|
|
}
|
|
|
|
if (!InitCreateSystemThreadsMsg(&m, CST_GHOST, pdesk, UniqueProcessId, bRemoteThread)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Since we are in CSRSS context use LpcRequestPort to send
|
|
* LPC_DATAGRAM message type. Do not use LpcRequestWaitReplyPort
|
|
* because it will send LPC_REQUEST which will fail (in server side).
|
|
*/
|
|
LeaveCrit();
|
|
Status = LpcRequestPort(CsrApiPort, (PPORT_MESSAGE)&m);
|
|
EnterCrit();
|
|
|
|
if (gpEventScanGhosts == NULL) {
|
|
return FALSE;
|
|
}
|
|
if (!NT_SUCCESS(Status)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (!(TestWF(pwnd, WFINDESTROY) || TestWF(pwnd, WFDESTROYED))) {
|
|
return AddGhostOwnersAndOwnees(pwnd);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* RemoveGhost
|
|
*
|
|
* This function is called from xxxFreeWindow to check and takes care
|
|
* of business when pwnd is either a ghost or a hung window.
|
|
\***************************************************************************/
|
|
VOID RemoveGhost(
|
|
PWND pwnd)
|
|
{
|
|
PGHOST* ppghost;
|
|
PGHOST pghost;
|
|
|
|
CheckCritIn();
|
|
for (ppghost = &gpghostFirst; *ppghost != NULL;
|
|
ppghost = &(*ppghost)->pghostNext) {
|
|
|
|
pghost = *ppghost;
|
|
|
|
/*
|
|
* If this window matches the hung window, then set an event to
|
|
* destroy the corresponding ghost window. If the ghost window hasn't
|
|
* been created yet, we can nuke the structure in context.
|
|
*/
|
|
if (pghost->pwnd == pwnd) {
|
|
if (pghost->pwndGhost == NULL) {
|
|
UnlinkAndFreeGhost(ppghost, pghost);
|
|
} else {
|
|
pghost->pwnd = NULL;
|
|
KeSetEvent(gpEventScanGhosts, EVENT_INCREMENT, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If this window matches the ghost window, just remove the
|
|
* structure from the list.
|
|
*/
|
|
if (pghost->pwndGhost == pwnd) {
|
|
UnlinkAndFreeGhost(ppghost, pghost);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* PaintGhost
|
|
*
|
|
* Draw the ghost window look.
|
|
\***************************************************************************/
|
|
VOID PaintGhost(
|
|
PWND pwnd,
|
|
HDC hdc)
|
|
{
|
|
PGHOST pghost;
|
|
HBITMAP hbmOld;
|
|
RECT rc;
|
|
LONG cx, cy;
|
|
#if GHOST_AGGRESSIVE
|
|
HFONT hfont, hfontOld;
|
|
WCHAR szHung[MAXSTRING];
|
|
ULONG cch;
|
|
SIZE size;
|
|
LONG xText;
|
|
LOGFONTW lf;
|
|
#endif
|
|
|
|
pghost = GhostFromGhostPwnd(pwnd);
|
|
if (pghost == NULL) {
|
|
return;
|
|
}
|
|
|
|
rc.left = rc.top = 0;
|
|
rc.right = pwnd->rcClient.right - pwnd->rcClient.left;
|
|
rc.bottom = pwnd->rcClient.bottom - pwnd->rcClient.top;
|
|
|
|
if (pghost->hbm != NULL) {
|
|
cx = pghost->rcClient.right - pghost->rcClient.left;
|
|
cy = pghost->rcClient.bottom - pghost->rcClient.top;
|
|
|
|
hbmOld = GreSelectBitmap(ghdcMem, pghost->hbm);
|
|
GreExtSelectClipRgn(hdc, pghost->hrgn, RGN_COPY);
|
|
|
|
GreBitBlt(hdc, pghost->rcClient.left, pghost->rcClient.top,
|
|
cx, cy, ghdcMem, 0, 0, SRCCOPY, 0);
|
|
|
|
GreSelectBitmap(ghdcMem, hbmOld);
|
|
|
|
SetRectRgnIndirect(ghrgnGDC, &rc);
|
|
SubtractRgn(ghrgnGDC, ghrgnGDC, pghost->hrgn);
|
|
GreExtSelectClipRgn(hdc, ghrgnGDC, RGN_COPY);
|
|
}
|
|
|
|
FillRect(hdc, &rc, SYSHBR(WINDOW));
|
|
|
|
GreExtSelectClipRgn(hdc, NULL, RGN_COPY);
|
|
|
|
#if GHOST_AGGRESSIVE
|
|
ServerLoadString(hModuleWin, STR_HUNG, szHung, ARRAY_SIZE(szHung));
|
|
cch = wcslen(szHung);
|
|
|
|
GreSetTextColor(hdc, RGB(0, 0, 255));
|
|
GreSetBkColor(hdc, RGB(255, 255, 0));
|
|
|
|
GreExtGetObjectW(gpsi->hCaptionFont, sizeof(LOGFONTW), &lf);
|
|
lf.lfHeight = (lf.lfHeight * 3) / 2;
|
|
lf.lfWeight = FW_BOLD;
|
|
hfont = GreCreateFontIndirectW(&lf);
|
|
hfontOld = GreSelectFont(hdc, hfont);
|
|
|
|
GreGetTextExtentW(hdc, szHung, cch, &size, GGTE_WIN3_EXTENT);
|
|
xText = max(0, ((rc.right - rc.left) - size.cx) / 2);
|
|
GreExtTextOutW(hdc, xText, 0, 0, NULL, szHung, cch, NULL);
|
|
|
|
GreSelectFont(hdc, hfontOld);
|
|
GreDeleteObject(hfont);
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxGhostWndProc
|
|
*
|
|
* Processes messages for ghost windows.
|
|
\***************************************************************************/
|
|
LRESULT xxxGhostWndProc(
|
|
PWND pwnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
PGHOST pghost;
|
|
|
|
VALIDATECLASSANDSIZE(pwnd, uMsg, wParam, lParam, FNID_GHOST, WM_NCCREATE);
|
|
|
|
switch (uMsg) {
|
|
case WM_CLOSE:
|
|
pghost = GhostFromGhostPwnd(pwnd);
|
|
|
|
/*
|
|
* Do the end task on the hung thread when the user tries to close
|
|
* the ghost window.
|
|
*/
|
|
if (pghost != NULL && pghost->pwnd != NULL) {
|
|
PostShellHookMessages(HSHELL_ENDTASK, (LPARAM)HWq(pghost->pwnd));
|
|
}
|
|
return 0;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
pghost = GhostFromGhostPwnd(pwnd);
|
|
if (pghost != NULL) {
|
|
if (pghost->fWarningText) {
|
|
return 0;
|
|
} else {
|
|
pghost->fWarningText = TRUE;
|
|
}
|
|
}
|
|
xxxAddWarningText(pwnd);
|
|
return 0;
|
|
|
|
case WM_SIZE:
|
|
/*
|
|
* Since we have wrapped, flowing text, repaint it when sizing.
|
|
*/
|
|
xxxInvalidateRect(pwnd, NULL, TRUE);
|
|
return 0;
|
|
|
|
case WM_ERASEBKGND:
|
|
PaintGhost(pwnd, (HDC)wParam);
|
|
return 1;
|
|
|
|
case WM_SETCURSOR:
|
|
/*
|
|
* Show the hung app cursor over the client.
|
|
*/
|
|
if (LOWORD(lParam) == HTCLIENT) {
|
|
zzzSetCursor(SYSCUR(WAIT));
|
|
return 1;
|
|
}
|
|
|
|
case WM_EXITSIZEMOVE:
|
|
pghost = GhostFromGhostPwnd(pwnd);
|
|
if (pghost != NULL) {
|
|
pghost->fSizedOrMoved = TRUE;
|
|
}
|
|
|
|
/*
|
|
* FALL THROUGH to DWP.
|
|
*/
|
|
|
|
default:
|
|
return xxxDefWindowProc(pwnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
#endif
|