/****************************** Module Header ******************************\
* Module Name: hungapp.c
*
* Copyright (c) 1985-95, Microsoft Corporation
*
*
* History:
* 03-10-92 DavidPe      Created.
\***************************************************************************/

#include "precomp.h"
#pragma hdrstop


/*
 * Millisecond defines for hung-app heuristics.
 */
#define HUNGAPPDEMONFREQUENCY           1000
#define FOREVER                         (DWORD)(-1)


/***************************************************************************\
* SetHungFlag
*
* Sets the specified redraw-if-hung flag in the window and adds the
* window to the list of windows to redraw if hung.
*
* 08-23-93  JimA        Created.
\***************************************************************************/

VOID SetHungFlag(
    PWND pwnd,
    WORD wFlag)
{

    /*
     * If the window has no hung redraw bits set and it's a top-level
     * window, add it to the redraw list.
     */
    if (!TestWF(pwnd, WFANYHUNGREDRAW) && pwnd->spwndParent == PWNDDESKTOP(pwnd)) {

        /*
         * No more free entries left in the list, so expand it.
         */
        if (gphrl->iFirstFree == gphrl->cEntries) {
            PHUNGREDRAWLIST p;
            DWORD dwSize = gphrl->cEntries * sizeof(PWND) +
                    sizeof(HUNGREDRAWLIST);

            p = UserReAllocPool(gphrl, dwSize, sizeof(HUNGREDRAWLIST) +
                    ((CHRLINCR + gphrl->cEntries - 1) * sizeof(PWND)),
                            TAG_HUNGLIST);

            /*
             * If the realloc failed, we'll simply set the flags and
             * return.  This window won't be redrawn if hung.
             */
            if (p == NULL) {
                SetWF(pwnd, wFlag);
                return;
            }

            /*
             * Put the newly allocated entries on the free list.
             */
            RtlZeroMemory(&p->apwndRedraw[p->cEntries],
                    CHRLINCR * sizeof(PWND));
            gphrl = p;
            gphrl->cEntries += CHRLINCR;
        }

        /*
         * Have the window reference the first free entry.
         */
        pwnd->iHungRedraw = gphrl->iFirstFree;
        gphrl->apwndRedraw[gphrl->iFirstFree++] = pwnd;
    }

    SetWF(pwnd, wFlag);
}


/***************************************************************************\
* ClearHungFlag
*
* Clears the specified redraw-if-hung flag in the window and if no other
* redraw-if-hung flags remain, remove the window from list of windows
* to be redrawn if hung.
*
* 08-23-93  JimA        Created.
\***************************************************************************/

VOID ClearHungFlag(
    PWND pwnd,
    WORD wFlag)
{
    ClrWF(pwnd, wFlag);
    if (!TestWF(pwnd, WFANYHUNGREDRAW) && pwnd->iHungRedraw != -1) {
        int iEnd = gphrl->iFirstFree - 1;

        /*
         * Remove the window from the redraw list and compact it.  If
         * the entry is not the last in the list, move the last
         * entry in the list into the spot being freed.
         */
        if (pwnd->iHungRedraw != iEnd) {
            gphrl->apwndRedraw[pwnd->iHungRedraw] = gphrl->
                    apwndRedraw[iEnd];
            gphrl->apwndRedraw[iEnd]->iHungRedraw = pwnd->iHungRedraw;
        }

        /*
         * Free the last entry.
         */
        gphrl->apwndRedraw[iEnd] = NULL;
        gphrl->iFirstFree--;

        pwnd->iHungRedraw = -1;
    }
}


/***************************************************************************\
* FHungApp
*
*
* 02-28-92  DavidPe     Created.
\***************************************************************************/

BOOL FHungApp(
    PTHREADINFO pti,
    DWORD dwTimeFromLastRead)
{

    /*
     * An app is considered hung if it isn't waiting for input, isn't in
     * startup processing, and hasn't called PeekMessage() within the
     * specified timeout.
     */
    if (!(pti->ppi->W32PF_Flags & W32PF_APPSTARTING) &&
            !(pti->pcti->fsWakeMask & (QS_MOUSE | QS_KEY)) &&
            ((NtGetTickCount() - GET_TIME_LAST_READ(pti)) > dwTimeFromLastRead)) {
        return TRUE;
    }

    return FALSE;
}


/***************************************************************************\
* RedrawHungWindowFrame
*
*
* 02-28-92  DavidPe     Created.
\***************************************************************************/

VOID RedrawHungWindowFrame(
    PWND pwnd,
    BOOL fActive)
{
    HDC hdc;
    TL tlpwnd;
    UINT wFlags = DC_NC | DC_NOSENDMSG;

    ThreadLock(pwnd, &tlpwnd);

    if (fActive)
        wFlags |= DC_ACTIVE;

    hdc = _GetDCEx(pwnd, NULL, DCX_USESTYLE | DCX_WINDOW);
    xxxDrawCaptionBar(pwnd, hdc, wFlags);
    _ReleaseDC(hdc);

    ThreadUnlock(&tlpwnd);
}


/***************************************************************************\
* RedrawHungWindow
*
* If the hrgnFullDrag is NULL, redraw the hung window's entire update region,
* otherwise, only redraw the intersection of the window's update region
* with the FullDrag region.
*
* 02-28-92  DavidPe     Created.
\***************************************************************************/

VOID RedrawHungWindow(
    PWND pwnd,
    HRGN hrgnFullDrag)
{
    HDC hdc;
    HBRUSH hbr;
    HRGN hrgnUpdate;
    RECT rc;
    TL tlpwnd;
    UINT flags;
    W32PID sid;
    DWORD dwColor;

    CheckCritIn();

    if (pwnd->hrgnUpdate == NULL) {
        return;
    }
    /*
     * First calculate hrgnUpdate.
     */
    if (pwnd->hrgnUpdate > MAXREGION) {
        hrgnUpdate = GreCreateRectRgn(0, 0, 0, 0);
        if (hrgnUpdate == NULL) {
            hrgnUpdate = MAXREGION;

        } else if (CopyRgn(hrgnUpdate, pwnd->hrgnUpdate) == ERROR) {
            GreDeleteObject(hrgnUpdate);
            hrgnUpdate = MAXREGION;
        }

    } else {

        /*
         * For our purposes, we need a real hrgnUpdate, so try and
         * create one if even if the entire window needs updating.
         */
        CopyRect(&rc, &pwnd->rcWindow);
        hrgnUpdate = GreCreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
        if (hrgnUpdate == NULL) {
            hrgnUpdate = MAXREGION;
        }
    }

    /*
     * If we're redrawing because we're full dragging and if the window's
     * update region does not intersect with the Full drag
     * update region, don't erase the hung window again. This is to prevent
     * flickering when a window has been invalidated by another window doing
     * full drag and hasn't received the paint message yet.
     * This way, only if there is a new region that has been invalidated will
     * we redraw the hung window.
     */
    if (hrgnFullDrag && hrgnUpdate != MAXREGION &&
            IntersectRgn(hrgnUpdate, hrgnUpdate, hrgnFullDrag) == NULLREGION) {
        GreDeleteObject(hrgnUpdate);
        return;
    }

    ThreadLock(pwnd, &tlpwnd);

    hdc = _GetDCEx(pwnd, hrgnUpdate, DCX_USESTYLE | DCX_WINDOW |
            DCX_INTERSECTRGN | DCX_NODELETERGN | DCX_LOCKWINDOWUPDATE);
    xxxDrawWindowFrame(pwnd, hdc, TRUE, TestwndFrameOn(pwnd));
    _ReleaseDC(hdc);

    CopyRect(&rc, &pwnd->rcWindow);
    xxxCalcClientRect(pwnd, &rc, TRUE);
    GreSetRectRgn(hrgnInv2, rc.left, rc.top, rc.right, rc.bottom);

    if (hrgnUpdate > MAXREGION) {
        switch (IntersectRgn(hrgnUpdate, hrgnUpdate, hrgnInv2)) {

        case ERROR:
            GreDeleteObject(hrgnUpdate);
            hrgnUpdate = MAXREGION;
            break;

        case NULLREGION:
            /*
             * There is nothing in the client area to repaint.
             * Blow the region away, and decrement the paint count
             * if possible.
             */
            GreDeleteObject(hrgnUpdate);
            hrgnUpdate = NULL;
            break;
        }
    }

    /*
     * Erase the rest of the window.
     */

    /*
     * Get a window dc so that the menu and scroll bar areas are erased
     * appropriately. But make sure it is clipped so that the children
     * get clipped out correctly! If we don't do this, this we could erase
     * children that aren't invalid.
     *
     * Note: DCX_WINDOW and DCX_USESTYLE will never clip out children.
     * Need to pass the clipping styles in directly, instead of passing
     * DCX_USESTYLE.
     */
    flags = DCX_INTERSECTRGN | DCX_WINDOW | DCX_CACHE;
    if (TestWF(pwnd, WFCLIPSIBLINGS))
        flags |= DCX_CLIPSIBLINGS;
    if (TestWF(pwnd, WFCLIPCHILDREN))
        flags |= DCX_CLIPCHILDREN;

    hdc = _GetDCEx(pwnd, hrgnUpdate, flags);

    if (pwnd == pwnd->head.rpdesk->pDeskInfo->spwndBkGnd) {

        InternalPaintDesktop((PDESKWND)(PWNDDESKTOP(pwnd)), hdc, TRUE);

    } else {

         rc = pwnd->rcWindow;

         OffsetRect(&rc, -pwnd->rcWindow.left, -pwnd->rcWindow.top);

         /*
          * Erase the rest of the window using the window' class background
          * brush.
          */
         if ((hbr = pwnd->pcls->hbrBackground) != NULL) {
             if ((DWORD)hbr <= COLOR_ENDCOLORS + 1)
                 hbr = ahbrSystem[(DWORD)hbr - 1];
         } else {
             /*
              * Use the window brush for windows and 3.x dialogs,
              * Use the COLOR3D brush for 4.x dialogs.
              */
             if (TestWF(pwnd, WFDIALOGWINDOW) && TestWF(pwnd, WFWIN40COMPAT))
                 hbr = SYSHBR(3DFACE);
             else
                 hbr = SYSHBR(WINDOW);
         }

        /*
         * If the window's class background brush is public, use it.
         */
        sid = (W32PID)GreGetObjectOwner((HOBJ)hbr, BRUSH_TYPE);
        if (sid == (W32PID)GetCurrentProcessId() || sid == OBJECT_OWNER_PUBLIC) {

            FillRect(hdc, &rc, hbr);

        } else {

            /*
             * The window's class background brush is not public.
             * We get its color and set the color of our own public brush and use
             * that for the background brush.
             */

            /*
             * If the window is a console window, get the console background brush.
             * This brush will be different than the console class brush if the user
             * changed the console background color.
             */
            if (gatomConsoleClass == pwnd->pcls->atomClassName) {

                dwColor = _GetWindowLong(pwnd, GWL_CONSOLE_BKCOLOR, FALSE);

            } else {

                if ((dwColor = GreGetBrushColor(hbr)) == -1)
                    dwColor = GreGetBrushColor(SYSHBR(WINDOW));
            }

            GreSetSolidBrush(hbrHungApp, dwColor);

            FillRect(hdc, &rc, hbrHungApp);
        }
    }
    _ReleaseDC(hdc);

    /*
     * The window has been erased and framed. It only did this because the
     * app hasn't done it yet:
     *
     * - the app hasn't erased and frame yet.
     * - the app is in the middle of erasing and framing.
     *
     * The app could not of completed erasing and framing, because the
     * WFREDRAWIFHUNG bit is cleared when this successfully completes.
     *
     * Given that the app may be in the middle of erasing and framing, we
     * need to set both the erase and frame bits *again* so it erasing and
     * frames over again (if we don't, it never will). If the app hasn't
     * done any erasing/framing yet, this is a nop.
     */
    SetWF(pwnd, WFSENDNCPAINT);
    SetWF(pwnd, WFSENDERASEBKGND);

#if 0
    /*
     * Always set WFUPDATEDIRTY: we don't want the app to draw, then stop
     * and have the hung app thread draw, and then allow the app to validate
     * itself: Mark the update region dirty - cannot be validated until the
     * app calls a painting function and acknowledges the update region.
     */

    /*
     * 7/7/96, vadimg: This causes a problem with apps that call
     * GetUpdateRect/Rgn which clears the WFUPDATEDIRTY flag.  If the 
     * HungAppMonitor kicks in in the middle of the paint, the flag will be 
     * incorrectly set, and the app's ValidateRect will not succeed.  Example:
     * VisualSlick Edit.  It doesn't matter if we set the flag here, 
     * InternalInvlaidate3 sets when necessary anyway.
     */

    SetWF(pwnd, WFUPDATEDIRTY);
#endif

#ifdef WIN95DOESTHIS
    /*
     * Go through all the children and redraw hung ones too.
     */
    if (hrgnFullDrag != NULL) {
        PWND pwndT;

        for (pwndT = pwnd->spwndChild; pwndT != NULL; pwndT = pwndT->spwndNext) {

            if (TestWF(pwndT, WFREDRAWIFHUNG) && (FHungApp(GETPTI(pwndT), CMSHUNGAPPTIMEOUT))) {
                ClearHungFlag(pwndT, WFREDRAWIFHUNG);
                RedrawHungWindow(pwndT, NULL);
            }

            if (TestWF(pwndT, WFDESTROYED)) {
                break;
            }
        } /* for */
    }
#endif

    ThreadUnlock(&tlpwnd);
}


/***************************************************************************\
* HungAppDemon
*
* NOTE: RIT timers (like this one) get called while inside an EnterCrit block.
*
* We keep a list of redraw-if-hung windows in a list that remains in a
* single page to avoid touching the windows themselves each time through
* this routine.  Touching the windows causes a bunch of unnecessary paging
* and in effect keeps all of the pages that contain top-level windows
* resident at all times; this is very wasteful.
*
* 02-28-92  DavidPe     Created.
\***************************************************************************/

LONG HungAppDemon(
    PWND pwnd,
    UINT message,
    DWORD wParam,
    LONG lParam)
{
    PWND *ppwndRedraw;
    int cEntries;
    UNREFERENCED_PARAMETER(message);
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);

    /*
     * See if we should start the screen saver.
     */
    IdleTimerProc();

    /*
     * If it is time to hide the app starting cursor, do it.
     */
    if (NtGetTickCount() >= gtimeStartCursorHide) {
        CalcStartCursorHide(NULL, 0);
    }

    /*
     * Now check to see if there are any top-level
     * windows that need redrawing.
     */
    if (grpdeskRitInput == NULL || grpdeskRitInput->pDeskInfo->spwnd == NULL)
        return 0;

    /*
     * Walk down the list of redraw-if-hung windows.  Loop
     * until we hit the end of the array or find a NULL.
     */
    cEntries = gphrl->cEntries;
    for (ppwndRedraw = gphrl->apwndRedraw; cEntries > 0 &&
            *ppwndRedraw != NULL; ) {

        /*
         * See if the app is hung.  If so, do the appropriate
         * redrawing.
         */
        pwnd = *ppwndRedraw;
        if (FHungApp(GETPTI(pwnd), CMSHUNGAPPTIMEOUT)) {
            if (TestWF(pwnd, WFREDRAWFRAMEIFHUNG)) {

                /*
                 * WFREDRAWFRAMEIFHUNG will be cleared in the process
                 * of drawing the frame, no need to clear it here.
                 */
                RedrawHungWindowFrame(pwnd, TestwndFrameOn(pwnd));
            }

            if (TestWF(pwnd, WFREDRAWIFHUNG)) {
                ClearHungFlag(pwnd, WFREDRAWIFHUNG);
                RedrawHungWindow(pwnd, NULL);
            }
        }

        /*
         * Step to the next entry only if the flags have been cleared,
         * i.e. pwnd != *ppwndRedraw.
         */
        if (pwnd == *ppwndRedraw) {
            ppwndRedraw++;
            cEntries--;
        }
    }

    return 0;
}