/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = VVEVENT.C
 *
 * DESCRIPTIVE NAME = Virtual Video Video Event Processing
 *
 *
 * VERSION = V2.0
 *
 * DATE      11/10/88
 *
 * DESCRIPTION  This module contains the VVD's video event support.
 *
 * FUNCTIONS    AddEvent()                Add event to the "event queue"
 *              vvFlushEvent()            Flush event from the "event queue"
 *              vvFlushEventNotify()      Wake up thread(s) in vvFlushEvent
 *              vvDeleteEvent()           Delete event from the "event queue"
 *              vvGetEvent()              Examine "event queue" for available events
 *              VVCheckForEvents()        Enumeration handler to check for video events
 *              vvCheckForDirtyLVB()      See if LVB is dirty, post event if it is
 *              vvCheckForDirtyPalette()  See if palette is dirty, post event if it is
 *              vvEnumerateVDMs()         Replacement for VDHEnumerateVDMs
 *              VVEventTimer()            Global context timer handler.
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/


#include <mvdm.h>
#include <vvd.h>
#include "vvdp.h"

#ifdef   VDDSTRICT
MODNAME = __FILE__;
#endif
extern ULONG flVVD;
#pragma  BEGIN_SWAP_DATA
extern HVDM hvdmFocus;
extern HVDM hvdmUpdate;
extern LSENTRY lseHeadActive;
extern ULONG nWindowedEvents;
extern HMXSEM hmxWindowedEvent;
extern HEVSEM hevWindowedEvent;
extern ULONG nWindowedVDMs;
extern ULONG nPMWindowedVDMs;
extern ULONG nMinWindowedVDMs;
extern HHOOK hhookEventTimer;
#ifdef  SVGA                                            /*            */
extern  ULONG   ulSVGAAdapterType;
extern  PFNSVGAGETBANK  apfnSVGAGetBank[];
#endif                                                  /*            */

#pragma  END_SWAP_DATA
#pragma  BEGIN_SWAP_CODE

/***************************************************************************
 *
 * FUNCTION NAME = AddEvent()
 *
 * DESCRIPTION   = Add event to the "event queue"
 *
 *
 * INPUT         = hvdm   -> VDM
 *                 iEvent == event ordinal
 *                 pEvent -> event data (varies from one event to another)
 *                 flAdd  == Event flags (eg, EVENT_FLUSH, EVENT_OWNER)
 *
 * OUTPUT        = None
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID PRIVENTRY vvAddEvent(HVDM hvdm,INT iEvent,register PVOID pEvent,FLAGS
                           flAdd)
{
  FLAGS fl;
  register LONG rowEnd;
  LONG colEnd,colDiff;
  register PVDMDATA pvd = pVDMData(hvdm);

  /*
  ** If this is an internally-generated event
  */

  if (!(flAdd&POSTEVENT_ADD))

    /*
    ** Then allow only if we own both device and display
    */

    if (iEvent==VVDEVENT_DDE)                                      /*            */
    {                                                              /*            */
      if ((pvd->flVDMXVideo & (VDMX_DEVOWNER)) != (VDMX_DEVOWNER)) /*            */
        return;                                                    /*            */
    }                                                              /*            */
    else                                                           /*            */
    {                                                              /*            */
      /*
      ** Then allow only if we own both device and display
      */

      if ((pvd->flVDMXVideo & (VDMX_DEVOWNER|VDMX_DSPOWNER)) != (VDMX_DEVOWNER|
         VDMX_DSPOWNER))
        return;
    }                                                              /*            */

  /*
  ** If we're not windowed, the only allowed events are PASTE and up
  */

  if ((flVVD&VVD_PMBGND && pvd->flVDMVideo&VDM_PMWINDOWED) || (pvd->flVDMVideo
     &(VDM_WINDOWED|VDM_MINIMIZED)) != VDM_WINDOWED)

    if (iEvent < VVDEVENT_ENDPASTE)
      return ;
  fl = 1 << iEvent-1;

/*     ** Make another quick test to see if we care about the event **          */
/*    if (!(pvd->flVDMXVideo & VDMX_SYNCOUTPUT))                             */
/*        if (fl & (1<<VVDEVENT_SCROLL-1 | 1<<VVDEVENT_STRING-1))            */
/*            return;                                                        */

  if (!(flAdd&EVENT_OWNER))
    AssertRC(RequestMutexSem(hmxWindowedEvent));

  switch (iEvent)
  {
    case  VVDEVENT_MODE :

      /*
      ** This is a workaround to allow other VDDs to tell the Shield
      ** that the VDM is now suspended until it runs full-screen
      */

      if (!pEvent)
        pvd->vvModeEvent.vvm_fSuspended = SUSPEND_UNSUPPORTED_MODE;

      /*
      ** If there is a change in mode data, report it
      */

      else

        if (!memcmp(&pvd->vvModeEvent,
                    pEvent,
                    sizeof(VVMODE)))

          fl = 0;

        else
        {
          pvd->flVDMVideo |= VDM_MODECHANGED;
          pvd->vvModeEvent = *(PVVMODE)pEvent;

#ifdef   VDDDEBUGALL

          if (!pvd->vvMode.vvm_fSuspended)
          {
            PRINTDEBUG("Visible region at %08x (%04x bytes)\n",
                       pvd->pvdmPhysVRAMVisible,
                       pvd->nbPhysVRAMVisible);
            PRINTDEBUG
               ("Screen dimensions %04x,%04x  Cell dimensions %04x,%04x\n",
                pvd->vvMode.vvm_nCols,
                pvd->vvMode.vvm_nRows,
                pvd->vvMode.vvm_ulCellWidth,
                pvd->vvMode.vvm_ulCellHeight);
          }
#endif
        }
      break;
    case  VVDEVENT_LVB :
      pvd->flDirtyPageEvent = *(PFLAGS)pEvent;
      break;
    case  VVDEVENT_SCROLL :
      pvd->vvScrollEvent = *(PVVSCROLL)pEvent;
      break;
    case  VVDEVENT_STRING :

      /*
      ** If string starts past visible area, skip it
      */

      if (((PVVSTRING)pEvent)->vva_row >= pvd->vvMode.vvm_nRows)
      {
        fl = 0;
        break;
      }

      /*
      ** If string falls off end of visible area, adjust it
      */

      rowEnd = ((PVVSTRING)pEvent)->vva_row;
      colEnd = ((PVVSTRING)pEvent)->vva_col+((PVVSTRING)pEvent)->vva_nChars;

      if (colEnd >= pvd->vvMode.vvm_nCols)
      {
        rowEnd += colEnd/pvd->vvMode.vvm_nCols;
        colEnd = colEnd%pvd->vvMode.vvm_nCols;

        if (rowEnd >= pvd->vvMode.vvm_nRows)
        {
          colEnd = pvd->vvMode.vvm_nCols-((PVVSTRING)pEvent)->vva_col;
          rowEnd = pvd->vvMode.vvm_nRows-((PVVSTRING)pEvent)->vva_row-1;
          ((PVVSTRING)pEvent)->vva_nChars = rowEnd *pvd->vvMode.vvm_nCols+
             colEnd;
        }
      }

      /*
      ** Setting nPeeks to just under threshold keeps us close
      ** to flushing, while setting the holding bit gives additional
      **   string events a chance to arrive
      */

      pvd->flEventsHolding |= fl;

      if (!(pvd->flVDMVideo&VDM_KEYREAD))
        pvd->nPeeks = PEEKS_CHECK-6;

      /*
      ** If a string event exists, see if we can coalesce
      */

      if (pvd->flEventsPending&fl)
      {
        rowEnd = pvd->vvStringEvent.vva_row;
        colEnd = pvd->vvStringEvent.vva_col+pvd->vvStringEvent.vva_nChars;

        if (colEnd >= pvd->vvMode.vvm_nCols)
        {
          rowEnd += colEnd/pvd->vvMode.vvm_nCols;
          colEnd = colEnd%pvd->vvMode.vvm_nCols;

          /*
          ** If ending row/column is off-screen, force a flush
          */

          if (rowEnd >= pvd->vvMode.vvm_nRows)
            rowEnd = -1;
        }

        if (((PVVSTRING)pEvent)->vva_row == rowEnd && ((PVVSTRING)pEvent)->
           vva_col >= pvd->vvStringEvent.vva_col)
        {

          /*
          ** We can coalesce, so no new event needed
          */

          fl = 0;
          colEnd = ((PVVSTRING)pEvent)->vva_nChars+(((PVVSTRING)pEvent)->
             vva_col-colEnd);

          if (colEnd > 0)
            pvd->vvStringEvent.vva_nChars += colEnd;

          /*
          ** This next check essentially forces a flush after
          ** every full line of output;  depending on
          ** the app, it can be better to not impose this condition,
          ** so it could become an advanced property
          */

          if (pvd->vvStringEvent.vva_col+pvd->vvStringEvent.vva_nChars <
             pvd->vvMode.vvm_nCols)
            break;
        }

        /*
        ** If we couldn't coalesce, we flush the original event
        */

        vvFlushEvent(hvdm,
                     VVDEVENT_STRING,
                     EVENT_OWNER);
      }

      /*
      ** No such event currently exists, so transfer the event data
      */

      if (fl)
      {

#ifdef   VDDSTRICT

        if (pvd->flEventsPending&fl)
        {
          PRINTDEBUG("String flush failed\n");
        }
#endif

        /*
        ** We need to re-set the holding the bit, because it might have
        ** been cleared by the flush above
        */

        pvd->flEventsHolding |= fl;
        pvd->vvStringEvent = *(PVVSTRING)pEvent;
      }
      break;
    case  VVDEVENT_CURSOR :

      if (!((PVVCURSOR)pEvent)->vvc_fVisible &&
         !pvd->vvCursorEvent.vvc_fVisible || !memcmp(&pvd->vvCursorEvent,
                                                     pEvent,
                                                     sizeof(VVCURSOR)))
        fl = 0;

      else
      {
        pvd->flEventsHolding |= fl;
        pvd->vvCursorEvent = *(PVVCURSOR)pEvent;
      }
      break;
    case  VVDEVENT_INPUT :

      if (pvd->flVDMVideo&VDM_INPUTPOSTED || !memcmp(&pvd->vvInputEvent,
                                                     pEvent,
                                                     sizeof(VVCURSOR)))
        fl = 0;

      else
      {
        pvd->flVDMVideo |= VDM_INPUTPOSTED;
        pvd->vvInputEvent = *(PVVCURSOR)pEvent;
      }
      break;
    case  VVDEVENT_ENDPASTE :
      vvDeleteEvent(hvdm,
                    VVDEVENT_PASTE,
                    EVENT_OWNER);
      break;
    case  VVDEVENT_TITLECHANGE :

      if (!pEvent)
        pvd->szVDMTitle[0] = NULL;

      else
        strcpy(pvd->szVDMTitle,
               pEvent);
      break;
  }

  /*
  ** Add the event if it's not already added
  */

  if (fl)
  {

    if (!(pvd->flEventsPending&fl))
    {
      pvd->flEventsPending |= fl;
      nWindowedEvents++;
    }

    /*
    ** Flush the event if requested to
    */

    if (flAdd&EVENT_FLUSH)
    {
      vvFlushEvent(hvdm,
                   iEvent,
                   EVENT_OWNER);

#ifdef   VDDSTRICT

      if (pvd->flEventsPending&fl)
      {
        PRINTDEBUG("Event flush failed\n");
      }
#endif
    }

    /*
    ** Record posting of events since last INPUT event
    */
/*        if (iEvent != VVDEVENT_INPUT && (pvd->flVDMXVideo & VDMX_SYNCOUTPUT))          */

    if (iEvent != VVDEVENT_INPUT)
      pvd->flVDMVideo &= ~VDM_INPUTPOSTED;

    /*
    ** Next, reposition this VDM at the head of the active list, as
    ** long as the event was synchronously generated by the VDM itself
    */

    if (!(flAdd&EVENT_ASYNC) && !FIRSTENTRY(hvdm,
                                            Active))
    {
      REMOVEENTRY(hvdm,
                  Active);
      INSERTENTRY(hvdm,
                  Active);
    }

    /*
    ** Finally, wake the event thread if this is not a held event
    */

    if (pvd->flEventsPending&~pvd->flEventsHolding)
      PostEventSem(hevWindowedEvent);
  }

  /*
  ** Release exclusive access to event updating
  */

  if (!(flAdd&EVENT_OWNER))
    ReleaseMutexSem(hmxWindowedEvent);
}

/***************************************************************************
 *
 * FUNCTION NAME = vvFlushEvent()
 *
 * DESCRIPTION   = Flush event from the "event queue"
 *
 *                 Note that we enter inside the EventUpdate semaphore, and
 *                 exit inside it as well.  While we are flushing the event
 *                 however, we must temporarily release it.  Before we do
 *                 so, we record what event we are waiting for, set the
 *                 per-VDM ShieldSynced semaphore, clear the EventUpdate
 *                 semaphore, and wait for the per-VDM ShieldSynced to
 *                 clear.  ShieldSynced is not cleared until the event we
 *                 flagged has been processed.
 *
 *                 Flushing is ignored if PM is background or the VDM is not
 *                 an unminimized window.
 *
 * INPUT         = hvdm    -> hvdm
 *                 iEvent  == event ordinal
 *                 flFlush == Event flags (eg, EVENT_OWNER)
 *
 * OUTPUT        = None
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID PRIVENTRY vvFlushEvent(HVDM hvdm,INT iEvent,FLAGS flFlush)
{
  FLAGS fl;
  BOOL fSuccess;
  register PVDMDATA pvd = pVDMData(hvdm);

  /*
  ** Make a quick test to see if PM needs the event
  */

  if ((flVVD&VVD_PMBGND && pvd->flVDMVideo&VDM_PMWINDOWED) || (pvd->flVDMVideo
     &(VDM_WINDOWED|VDM_MINIMIZED)) != VDM_WINDOWED)
    return ;
  fl = 1 << iEvent-1;

/*   ** Make another quick test to see if we care about the event **          */
/*   if (!(pvd->flVDMXVideo & VDMX_SYNCOUTPUT))                             */
/*       if (fl & (1<<VVDEVENT_SCROLL-1 | 1<<VVDEVENT_STRING-1))            */
/*           return;                                                        */
  /*
  ** Insure control of the event update semaphore
  */

  if (!(flFlush&EVENT_OWNER))
    RequestMutexSem(hmxWindowedEvent);

  if (pvd->flEventsPending&fl)
  {

    /*
    ** Clear holding bit, and record event we are waiting for
    */

    pvd->flEventsHolding &= ~fl;
    pvd->flEventsFlushing |= fl;

    while (pvd->flEventsFlushing&fl)
    {

      /*
      ** Arm the ShieldSynced semaphore
      */

      ResetEventSem(pvd->hevShieldSynced);

      /*
      ** Release exclusive access to event updating
      */

      ReleaseMutexSem(hmxWindowedEvent);

      /*
      ** Start Shield processing
      */

      PostEventSem(hevWindowedEvent);

      /*
      ** Wait for Shield to process event
      */

      fSuccess = WaitEventSem(pvd->hevShieldSynced);

      /*
      ** See if the wait prematurely terminated (which we assume to
      ** mean the VDM terminated) or the event is really flushed
      */

      if (!fSuccess || !(pvd->flEventsFlushing&fl))
      {

        if (flFlush&EVENT_OWNER)
          RequestMutexSem(hmxWindowedEvent);
        return ;
      }

      /*
      ** If we're still waiting, retake the global semaphore
      ** to avoid a reset/post ShieldSynced semaphore race
      */

      RequestMutexSem(hmxWindowedEvent);
    }
  }

  /*
  ** If we took temporary control and we're still here, relinquish it
  */

  if (!(flFlush&EVENT_OWNER))
    ReleaseMutexSem(hmxWindowedEvent);
}

/***************************************************************************
 *
 * FUNCTION NAME = vvFlushEventNotify()
 *
 * DESCRIPTION   = Wake up thread(s) in vvFlushEvent
 *
 *
 * INPUT         = hvdm    -> hvdm
 *                 iEvent  == event ordinal flushed (0 implies all)
 *
 * OUTPUT        = None
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID PRIVENTRY vvFlushEventNotify(HVDM hvdm,INT iEvent)
{
  register PVDMDATA pvd = pVDMData(hvdm);

  RequestMutexSem(hmxWindowedEvent);

  if (!iEvent && vdhXCHG(&pvd->flEventsFlushing,
                         0) || iEvent && vdhBTR(&pvd->flEventsFlushing,
                                                iEvent-1))
    PostEventSem(pvd->hevShieldSynced);
  ReleaseMutexSem(hmxWindowedEvent);
}

/***************************************************************************
 *
 * FUNCTION NAME = vvDeleteEvent()
 *
 * DESCRIPTION   = Delete event from the "event queue"
 *
 *
 * INPUT         = hvdm    -> hvdm
 *                 iEvent  == event ordinal
 *                 flFlush == Event flags (eg, EVENT_OWNER)
 *
 * OUTPUT        = None
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID PRIVENTRY vvDeleteEvent(HVDM hvdm,INT iEvent,FLAGS flFlush)
{
  FLAGS fl;
  register PVDMDATA pvd = pVDMData(hvdm);

  /*
  ** Insure control of the event update semaphore
  */

  if (!(flFlush&EVENT_OWNER))
    RequestMutexSem(hmxWindowedEvent);
  fl = 1 << iEvent-1;

  if (pvd->flEventsPending&fl)
  {
    pvd->flEventsPending &= ~fl;
    pvd->flEventsHolding &= ~fl;
    nWindowedEvents--;

    /*
    ** Whenever we clear a pending bit,
    ** we must see if someone was waiting
    */

    if (pvd->flEventsFlushing&fl)
    {
      pvd->flEventsFlushing &= ~fl;
      PostEventSem(pvd->hevShieldSynced);
    }
  }

  /*
  ** If we took temporary control and we're still here, relinquish it
  */

  if (!(flFlush&EVENT_OWNER))
    ReleaseMutexSem(hmxWindowedEvent);
}

/***************************************************************************
 *
 * FUNCTION NAME = vvGetEvent()
 *
 * DESCRIPTION   = Examine "event queue" for available events
 *
 *                 Identify the highest priority event currently pending,
 *                 remove it, and return its ID to the caller.
 *
 * INPUT         = phvdm -> hvdm
 *                 fNoHold == TRUE to skip held events, FALSE (normal case) to skip none
 *
 * OUTPUT        = SUCCESS
 *                     Event ordinal
 *                 FAILURE
 *                     Zero
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

INT PRIVENTRY vvGetEvent(PHVDM phvdm,BOOL fNoHold)
{

  FLAGS fl;
  register INT i = 0;
  register PVDMDATA pvd;

  RequestMutexSem(hmxWindowedEvent);

  if (nWindowedEvents)
  {

    if (!vvEnumerateVDMs((PENUMHOOK)VVCheckForEvents,
                         FALSE))
    {
      pvd = pVDMData(*phvdm = hvdmUpdate);
      fl = pvd->flEventsPending;

      if (fNoHold)
        fl &= ~pvd->flEventsHolding;

      if (fl)
      {
        i = vdhBSF(fl)+1;
        AssertTRUE(i >= 0 && i <= VVDEVENT_MAX);

/*      if (i == VVDEVENT_SCROLL || i == VVDEVENT_STRING ||                 */
/*          i == VVDEVENT_CURSOR && pvd->mstateVideo <= MEMORY_FONT &&          */
/*          pvd->flVDMXVideo & VDMX_SYNCOUTPUT) {                           */
/*          if (vvCheckForDirtyLVB(pvd->hvdmVideo, EVENT_OWNER))            */
/*              i = VVDEVENT_LVB;                                           */
/*      }                                                                   */

        if (i == VVDEVENT_SCROLL || i == VVDEVENT_STRING || i ==
           VVDEVENT_CURSOR && pvd->mstateVideo <= MEMORY_FONT)
        {

          if (vvCheckForDirtyLVB(pvd->hvdmVideo,
                                 EVENT_OWNER))
            i = VVDEVENT_LVB;
        }

/*  vvCheckForDirtyLVB may return true without adding the         73061 */
/*  VVDEVENT_LVB event to the pending event queue (if no longer   73061 */
/*  windowed, for example).  If the VVDEVENT_LVB was not added,   73061 */
/*  nWindowedEvents was not incremented either and therefore      73061 */
/*  should not be decremented here.  nWindowedEvents used to be   73061 */
/*  decremented regardless.                                       73061 */

        fl = (1 << i-1);                                       /* 73061 */
        if (i == VVDEVENT_LVB) {                               /* 73061 */
           /* check to see if VVEVENT_LVB is in the event queue   73061 */
           if (pvd->flEventsPending & fl)                      /* 73061 */
              nWindowedEvents--;                               /* 73061 */
        } else                                                 /* 73061 */
           /* other events are pulled directly from the event     73061 */
           /* queue earlier in this rutn.                         73061 */
           nWindowedEvents--;                                  /* 73061 */

        fl = ~fl;                                              /* 73061 */
        /* clear the bit to remove event from the event queue     73061 */
        pvd->flEventsPending &= fl;
        pvd->flEventsHolding &= fl;
      }
    }
  }
  ReleaseMutexSem(hmxWindowedEvent);
  return  i;
}

/***************************************************************************
 *
 * FUNCTION NAME = VVCheckForEvents()
 *
 * DESCRIPTION   = Enumeration handler to check for video events
 *
 *                 This routine is called via the windowed VDM enumeration
 *                 handler from vvGetEvent (fPeriodic == FALSE) and
 *                 vvEventTimer (fPeriodic == TRUE), and from
 *                 vvKeyboardCheck when the VDM has done a keyboard read (or
 *                 some number of peeks).
 *
 *                 As such, it checks for events already posted but not yet
 *                 processed, as well as possible events not even posted yet
 *                 (like LVB and palette changes).
 *
 *                 NOTE that this will succeed (return FALSE) only if PM is
 *                 foreground AND the VDM is an unminimized window.
 *
 * INPUT         = hvdm -> VDM
 *                 fPeriodic == TRUE if this is a periodic check, FALSE if not
 *
 * OUTPUT        = SUCCESS
 *                     FALSE (stop enumeration)
 *                 FAILURE
 *                     TRUE (continue enumeration)
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

BOOL PRIVENTRY VVCheckForEvents(HVDM hvdm,BOOL fPeriodic)
{
  ULONG flHolding;
  BOOL fCheckDirty = FALSE;
  register PVDMDATA pvd = pVDMData(hvdm);

  /* The VDM_UPDATING test is an attempt to avoid unfairly preempting
  ** the PM event thread while it's busy updating a VDM window (which
  ** the global context timer hook can do) and to avoid prematurely
  ** invalidating the current setting of the holding bits for this VDM
  */

  if (pvd->flVDMVideo&VDM_UPDATING)
    return  TRUE;

  /* If MODECHANGING, we generally want to wait for the INT 10h to finish
  ** before processing any events, but if FROZEN, then it will never finish,
  ** and we need to tell the Shield that the VDM is frozen now....
  */

  if ((pvd->flVDMVideo&(VDM_MODECHANGING|VDM_FROZEN)) == VDM_MODECHANGING)
    if (!(pvd->flVDMX2Video & VDMX2_8514BLOCKED))            /*            */
    return  TRUE;

  flHolding = 0;

  if (fPeriodic)
  {
    flHolding = pvd->flEventsHolding;

    /*
    ** Skip periodic checks for minimized VDMs and what-not
    */

    if ((pvd->mstateVideo == MEMORY_NONE) || (flVVD&VVD_PMBGND &&
       pvd->flVDMVideo&VDM_PMWINDOWED) || (pvd->flVDMVideo&(VDM_WINDOWED|
       VDM_MINIMIZED)) != VDM_WINDOWED)
      return  TRUE;

    /*
    ** Now decide if we should also check for dirty page/palette info.
    ** That *may* be a reasonable thing to do on periodic keyboard input
    ** events, but not necessarily on every timer tick, which is why the
    ** TIMER case is a bit more "sophisticated" shall we say?
    */

    if (fPeriodic >= PERIODIC_KBDPEEK)
      flHolding = 0;

    if (fPeriodic == PERIODIC_KBDREAD)
      fCheckDirty++;

    else

      if (fPeriodic == PERIODIC_TIMER)
      {
        pvd->nPeriodic++;

        if (++pvd->nPeriodic > pvd->nPeriodicLimit)
        {

          /*
          ** If this is a PHYSPAGE VDM and nPeriodic hasn't gotten very
          ** large yet, hold off until the PHYSPAGECHK bit is set as well;
          ** if there's lots of page fault activity, PHYSPAGECHK will tend
          ** to remain off, and LVB updates will then occur over larger more
          ** meaningful intervals (the worst case being TIMER_DELAY)
          */

          if ((pvd->flVDMVideo&(VDM_PHYSPAGE|VDM_PHYSPAGECHK)) == VDM_PHYSPAGE
             )

            if (pvd->nPeriodic >= TIMER_DELAY/TIMER_CHECK)
              fCheckDirty++;

            else
              pvd->flVDMVideo |= VDM_PHYSPAGECHK;

          else
            fCheckDirty++;
        }
      }

    if (fCheckDirty)
    {

      /*
      ** I used to always clear the holding flags on any periodic
      ** check, but with the advent of adjustable per-VDM timer tick
      ** thresholds, it makes more sense to clear them here.  Note that
      ** they are cleared *before* the checks that follow, because it is
      ** possible those checks may add new holding bits (although in
      ** current implementation, they won't -- holding bits are currently
      ** only used for string and cursor events).  That's why I re-"or"
      ** any new holding bits with the initial copy....
      */

      pvd->flEventsHolding = 0;

      /*
      ** Order of checks should match priorities of events
      */

      if (!(pvd->flVDMXVideo&VDMX_SEAMLESS))     /*                         */

        if (vvCheckForDirtyPalette(hvdm,
                                   EVENT_OWNER|EVENT_ASYNC) ||
                                      vvCheckForDirtyLVB(hvdm,
                                                         EVENT_OWNER|
                                                         EVENT_ASYNC))
        {

/*     VDHWakeIdle(hvdm);                                                   */

          flHolding |= pvd->flEventsHolding;
        }
      pvd->flVDMVideo &= ~VDM_PHYSPAGECHK;
      pvd->nPeriodic = 0;
    }
  }

  if (pvd->flEventsPending&~flHolding)
  {

    if (!fPeriodic)
    {

      if (pvd->pidVideo != 0 && pvd->pidVideo != VDHQuerySysValue(NULL,
                                                                  VDHLSV_PID))
        return  TRUE;

      hvdmUpdate = hvdm;
    }
    return  FALSE;
  }
  return  TRUE;
}

/***************************************************************************
 *
 * FUNCTION NAME = vvCheckForDirtyLVB()
 *
 * DESCRIPTION   = See if LVB is dirty, post event if it is
 *
 *
 * INPUT         = hvdm == VDM handle
 *                 fl   == Event flags to post if dirty
 *
 * OUTPUT        = TRUE if VDM's LVB was dirty, FALSE if not
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

BOOL PRIVENTRY vvCheckForDirtyLVB(HVDM hvdm,FLAGS fl)
{
  FLAGS flDirtyTemp;
  BOOL fDirty = FALSE;
  register PVDMDATA pvd = pVDMData(hvdm);

#ifdef  VTEXT                                                           //J-TS00V
    if ((pvd->GaleData.flVtext & VDMV_VTEXT_INSTALLED) &&               //J-TS00V
        (pvd->GaleData.flVtext & VDMV_SUPPORTED_TEXT_MODE))             //J-TS00V
        return vvCheckForDirtyLVBVtext(hvdm, fl);                       //J-TS00V
#endif  //VTEXT                                                         //J-TS00V
 #ifdef SVGA
  if (pvd->flVDMXVideo & VDMX_ENHANCEDMODE)                  /*            */
//  if ((pvd->mstateVideo == MEMORY_GRFX256) &&
//      (pvd->vvMode.vvm_nCols > 320)) {
  {
        if (pvd->flDirtyPages |= VDHGetDirtyPageInfo(hvdm, (PVOID)0xA0000L, BANKSIZE/PAGESIZE))
            pvd->ulDirtyBankEvent = (*apfnSVGAGetBank[ulSVGAAdapterType])(hvdm, TRUE);

  } else {

        pvd->flDirtyPages |= VDHGetDirtyPageInfo(hvdm,
                                                 pvd->pvdmPhysVRAMActive,
                                                 pvd->npgPhysVRAMActive);

  }
 #else

  pvd->flDirtyPages |= VDHGetDirtyPageInfo(hvdm,
                                           pvd->pvdmPhysVRAMActive,
                                           pvd->npgPhysVRAMActive);
 #endif

  if (flDirtyTemp = vdhXCHG(&pvd->flDirtyPages,
                            0))
  {
    fDirty++;
    vvAddEvent(hvdm,
               VVDEVENT_LVB,
               SSToDS(&flDirtyTemp),
               fl);
  }

    /*
    ** In the non-synchronous output case, it looks better if the
    ** non-synchronous events are synchronized with themselves at least;
    ** so we don't post cursor events in that situation, just add them
    ** after LVB events.  Note that AddEvent is smart enough to not really
    ** generate an event if no change in cursor data has occurred (although
    ** a "dirtycursor" bit -- similar to the "dirtypalette" bit -- might
    ** be nice to eventually have.... )
    */

/*  if (!(pvd->flVDMXVideo & VDMX_SYNCOUTPUT))                              */
/*      vvAddEvent(hvdm, VVDEVENT_CURSOR, &pvd->vvCursor, fl);              */

  return  fDirty;
}

/***************************************************************************
 *
 * FUNCTION NAME = vvCheckForDirtyPalette()
 *
 * DESCRIPTION   = See if palette is dirty, post event if it is
 *
 *
 * INPUT         = hvdm == VDM handle
 *                 fl   == Event flags to post if dirty
 *
 * OUTPUT        = TRUE if VDM's LVB was dirty, FALSE if not
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

BOOL PRIVENTRY vvCheckForDirtyPalette(HVDM hvdm,FLAGS fl)
{
  BOOL fDirty = FALSE;
  register PVDMDATA pvd = pVDMData(hvdm);


  if (vdhBTR(&pvd->flVDMVideo,
             LOG2(VDM_DIRTYPALETTE)))
  {
    fDirty++;
    vvAddEvent(hvdm,
               VVDEVENT_PALETTE,
               NULL,
               fl);
  }
  return  fDirty;
}

/***************************************************************************
 *
 * FUNCTION NAME = vvEnumerateVDMs()
 *
 * DESCRIPTION   = Replacement for VDHEnumerateVDMs
 *
 *                 The reason for this replacement is to be fairer about
 *                 handling windowed VDM updates.  Whenever a VDM posts a
 *                 new event, it will be moved to the head of the active
 *                 list (see lseHeadActive), and we will search VDMs for
 *                 events in the order given by that list.
 *
 * INPUT         = pfn       -> worker function
 *                 fPeriodic == FALSE if removing events, TRUE if just
 *                              checking for them
 *
 * OUTPUT        = SUCCESS
 *                     returns TRUE
 *                 FAILURE
 *                     returns FALSE, worker stopped enumeration
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

BOOL PRIVENTRY vvEnumerateVDMs(PENUMHOOK pfnWorker,BOOL fPeriodic)
{
  register PLSENTRY plse;


  if (hvdmFocus)

    if (!(*pfnWorker)(hvdmFocus,
                      fPeriodic))
      return  FALSE;

  plse = lseHeadActive.lse_plseNext;

  while (plse)
  {

    if (plse->lse_hvdm != hvdmFocus)
    {

      if (!(*pfnWorker)(plse->lse_hvdm,
                        fPeriodic))
        return  FALSE;
    }

    plse = plse->lse_plseNext;
  }
  return  TRUE;
}

/***************************************************************************
 *
 * FUNCTION NAME = VVEventTimer()
 *
 * DESCRIPTION   = Global context timer handler to check for video events
 *
 *                 This is the global context hook used by the timer handler
 *                 so that we can check for events at task-time rather than
 *                 interrupt-time.
 *
 * INPUT         = p    == undefined
 *                 pcrf -> undefined
 *
 * OUTPUT        = None
 *
 * RETURN-NORMAL =
 * RETURN-ERROR  =
 *
 ****************************************************************************/

VOID HOOKENTRY VVEventTimer(PVOID p,PCRF pcrf)
{
  ULONG ulTime;


  if (!(nWindowedVDMs == 0 || nWindowedVDMs == nPMWindowedVDMs && flVVD
     &VVD_PMBGND || nWindowedVDMs == nMinWindowedVDMs))
  {
    flVVD &= ~VVD_TIMERARMED;             /* Event timer disarmed           */
    ulTime = TIMER_CHECK;
    RequestMutexSem(hmxWindowedEvent);

    /*
    ** If we have event(s) to post, clear the event semaphore
    ** AND force the timer to fire at the very next time slice,
    ** because this may be one of series of sequential events...
    */

    if (!vvEnumerateVDMs((PENUMHOOK)VVCheckForEvents,
                         PERIODIC_TIMER))
    {
      PostEventSem(hevWindowedEvent);

#ifdef   MAYBE
      ulTime = 0;
#endif
    }
    ReleaseMutexSem(hmxWindowedEvent);

    if (!(flVVD&VVD_TIMERARMED))                 /*                         */
      VDHArmTimerHook(hhookEventTimer,
                      ulTime,                    /*                         */
                      VDH_TIMER_GLOBAL_CONTEXT); /*                         */
  }
}

#pragma  END_SWAP_CODE

