home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / xwphescr.zip / XWPH0208.ZIP / src / helpers / timer.c < prev    next >
C/C++ Source or Header  |  2002-07-24  |  27KB  |  779 lines

  1.  
  2. /*
  3.  *@@sourcefile timer.c:
  4.  *      XTimers, which can be used to avoid excessive PM
  5.  *      timer usage.
  6.  *
  7.  *      The functions here allow you to share a single PM
  8.  *      timer for many windows on the same thread. Basically,
  9.  *      these start a "master PM timer", to whose WM_TIMER
  10.  *      message your window procedure must respond by calling
  11.  *      tmrTimerTick, which will "distribute" the timer tick
  12.  *      to those XTimers which have elapsed.
  13.  *
  14.  *      The main advantage of the XTimers is that these
  15.  *      are not a limited resource (as opposed to PM timers).
  16.  *      I don't know how many PM timers exist on the system,
  17.  *      but PMREF says that the no. of remaining timers can
  18.  *      be queried with WinQuerySysValue(SV_CTIMERS).
  19.  *
  20.  *      XTimers are used excessively by the default widgets
  21.  *      of XWorkplace's XCenter.
  22.  *
  23.  *      There are a few limitations with the XTimers though:
  24.  *
  25.  *      --  tmrTimerTick (which you must call when the
  26.  *          "master" WM_TIMER comes in) does not post WM_TIMER
  27.  *          messages to the windows specified in the subtimers,
  28.  *          but calls the window procedure of the target window
  29.  *          directly. This makes sure that timers work even if
  30.  *          some thread is hogging the SIQ.
  31.  *
  32.  *          This however requires that all XTimers must run on
  33.  *          the same thread as the owner window of the master
  34.  *          timer which was specified with tmrCreateSet.
  35.  *
  36.  *      --  Queue timers (with HWND == NULLHANDLE) are not
  37.  *          supported.
  38.  *
  39.  *      --  When a window is destroyed, its timers are not
  40.  *          automatically cleaned up. tmrTimerTick does
  41.  *          detect invalid windows and removes them from the
  42.  *          timers list before posting, but to be on the safe
  43.  *          side, always call tmrStopAllTimers when WM_DESTROY
  44.  *          comes into a window which has used timers.
  45.  *
  46.  *      So to use XTimers, do the following:
  47.  *
  48.  *      1.  Create a timer set with tmrCreateSet. Specify
  49.  *          an owner window and the timer ID of the master
  50.  *          PM timer.
  51.  *
  52.  *      2.  You can then start and stop XTimers for windows
  53.  *          on the same thread by calling tmrStartXTimer and
  54.  *          tmrStopXTimer, respectively.
  55.  *
  56.  *      3.  In the window proc of the owner window, respond
  57.  *          to WM_TIMER for the master PM timer by calling
  58.  *          tmrTimerTick. This will call the window procs
  59.  *          of those windows with WM_TIMER messages directly
  60.  *          whose XTimers have elapsed.
  61.  *
  62.  *      Function prefixes:
  63.  *      --  tmr*   timer functions
  64.  *
  65.  *@@header "helpers\timer.h"
  66.  *@@added V0.9.7 (2000-12-04) [umoeller]
  67.  */
  68.  
  69. /*
  70.  *      Copyright (C) 2000-2001 Ulrich Möller.
  71.  *      This file is part of the "XWorkplace helpers" source package.
  72.  *      This is free software; you can redistribute it and/or modify
  73.  *      it under the terms of the GNU General Public License as published
  74.  *      by the Free Software Foundation, in version 2 as it comes in the
  75.  *      "COPYING" file of the XWorkplace main distribution.
  76.  *      This program is distributed in the hope that it will be useful,
  77.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  78.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  79.  *      GNU General Public License for more details.
  80.  */
  81.  
  82. // OS2 includes
  83.  
  84. #define OS2EMX_PLAIN_CHAR
  85.     // this is needed for "os2emx.h"; if this is defined,
  86.     // emx will define PSZ as _signed_ char, otherwise
  87.     // as unsigned char
  88.  
  89. #define INCL_DOSEXCEPTIONS
  90. #define INCL_DOSPROCESS
  91. #define INCL_DOSSEMAPHORES
  92. #define INCL_DOSMISC
  93. #define INCL_DOSERRORS
  94.  
  95. #define INCL_WINWINDOWMGR
  96. #define INCL_WINMESSAGEMGR
  97. #define INCL_WINTIMER
  98. #include <os2.h>
  99.  
  100. #include <stdio.h>
  101. #include <setjmp.h>
  102.  
  103. #include "setup.h"                      // code generation and debugging options
  104.  
  105. #include "helpers\datetime.h"
  106. #include "helpers\except.h"
  107. #include "helpers\linklist.h"
  108. #include "helpers\math.h"
  109. #include "helpers\standards.h"
  110. #include "helpers\threads.h"
  111. #include "helpers\timer.h"
  112.  
  113. // #define DEBUG_XTIMERS
  114.  
  115. /*
  116.  *@@category: Helpers\PM helpers\Timer replacements
  117.  *      see timer.c.
  118.  */
  119.  
  120. /* ******************************************************************
  121.  *
  122.  *   Private declarations
  123.  *
  124.  ********************************************************************/
  125.  
  126. /*
  127.  *@@ XTIMER:
  128.  *      one of these represents an XTimer.
  129.  *      These are stored in a linked list in
  130.  *      an _XTIMERSET.
  131.  */
  132.  
  133. typedef struct _XTIMER
  134. {
  135.     USHORT     usTimerID;           // timer ID, as passed to tmrStartXTimer
  136.     HWND       hwndTarget;          // target window, as passed to tmrStartXTimer
  137.     ULONG      ulTimeout;           // timer's timeout (in ms)
  138.     ULONG      ulNextFire;          // next time scalar (from dtGetULongTime) to fire at
  139. } XTIMER, *PXTIMER;
  140.  
  141. /* ******************************************************************
  142.  *
  143.  *   Global variables
  144.  *
  145.  ********************************************************************/
  146.  
  147. HMTX                G_hmtxTimers = NULLHANDLE;  // timers lock mutex
  148.  
  149. /* ******************************************************************
  150.  *
  151.  *   Private functions
  152.  *
  153.  ********************************************************************/
  154.  
  155. /*
  156.  *@@ LockTimers:
  157.  *
  158.  *@@added V0.9.12 (2001-05-12) [umoeller]
  159.  */
  160.  
  161. static BOOL LockTimers(VOID)
  162. {
  163.     if (!G_hmtxTimers)
  164.         return (!DosCreateMutexSem(NULL,
  165.                                    &G_hmtxTimers,
  166.                                    0,
  167.                                    TRUE));      // request!
  168.     else
  169.         return !DosRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT);
  170. }
  171.  
  172. /*
  173.  *@@ UnlockTimers:
  174.  *
  175.  *@@added V0.9.12 (2001-05-12) [umoeller]
  176.  */
  177.  
  178. static VOID UnlockTimers(VOID)
  179. {
  180.     DosReleaseMutexSem(G_hmtxTimers);
  181. }
  182.  
  183. /*
  184.  *@@ FindTimer:
  185.  *      returns the XTIMER structure from the global
  186.  *      linked list which matches the given window
  187.  *      _and_ timer ID.
  188.  *
  189.  *      Internal function. Caller must hold the mutex.
  190.  */
  191.  
  192. static PXTIMER FindTimer(PXTIMERSET pSet,          // in: timer set (from tmrCreateSet)
  193.                          HWND hwnd,                // in: timer target window
  194.                          USHORT usTimerID)         // in: timer ID
  195. {
  196.     PLINKLIST pllXTimers;
  197.     if (    (pSet)
  198.          && (pllXTimers = (PLINKLIST)pSet->pvllXTimers)
  199.        )
  200.     {
  201.         PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
  202.         while (pNode)
  203.         {
  204.             PXTIMER pTimer = (PXTIMER)pNode->pItemData;
  205.             if (    (pTimer->usTimerID == usTimerID)
  206.                  && (pTimer->hwndTarget == hwnd)
  207.                )
  208.             {
  209.                 return (pTimer);
  210.             }
  211.  
  212.             pNode = pNode->pNext;
  213.         }
  214.     }
  215.  
  216.     return NULL;
  217. }
  218.  
  219. /*
  220.  *@@ RemoveTimer:
  221.  *      removes the specified XTIMER structure from
  222.  *      the global linked list of running timers.
  223.  *
  224.  *      Internal function. Caller must hold the mutex.
  225.  */
  226.  
  227. static VOID RemoveTimer(PXTIMERSET pSet,       // in: timer set (from tmrCreateSet)
  228.                         PXTIMER pTimer)        // in: timer to remove
  229. {
  230.     PLINKLIST pllXTimers;
  231.     if (    (pSet)
  232.          && (pllXTimers = (PLINKLIST)pSet->pvllXTimers)
  233.        )
  234.     {
  235.         #ifdef DEBUG_XTIMERS
  236.         _Pmpf((__FUNCTION__ ": removing timer %d", pTimer->usTimerID));
  237.         #endif
  238.         lstRemoveItem(pllXTimers,
  239.                       pTimer);       // auto-free!
  240.     }
  241. }
  242.  
  243. /*
  244.  *@@ AdjustPMTimer:
  245.  *      goes thru all XTimers in the sets and starts
  246.  *      or stops the PM timer with a decent frequency.
  247.  *
  248.  *      Internal function. Caller must hold the mutex.
  249.  *
  250.  *@@added V0.9.9 (2001-03-07) [umoeller]
  251.  *@@changed V0.9.14 (2001-07-07) [umoeller]: added GCD optimizations
  252.  */
  253.  
  254. static VOID AdjustPMTimer(PXTIMERSET pSet)
  255. {
  256.     PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
  257.     ULONG   cTimers = lstCountItems(pllXTimers);
  258.  
  259.     #ifdef DEBUG_XTIMERS
  260.         _Pmpf((__FUNCTION__ ": entering"));
  261.     #endif
  262.  
  263.     if (!cTimers)
  264.     {
  265.         // no XTimers running:
  266.         if (pSet->idPMTimerRunning)
  267.         {
  268.             // but PM timer running:
  269.             // stop it
  270.             WinStopTimer(pSet->hab,
  271.                          pSet->hwndOwner,
  272.                          pSet->idPMTimer);
  273.             pSet->idPMTimerRunning = 0;
  274.         }
  275.  
  276.         pSet->ulPMTimeout = 0;
  277.     }
  278.     else
  279.     {
  280.         // we have timers:
  281.  
  282.         ULONG       ulOldPMTimeout = pSet->ulPMTimeout;
  283.  
  284.         PLISTNODE   pNode = lstQueryFirstNode(pllXTimers);
  285.         PXTIMER     pTimer1 = (PXTIMER)pNode->pItemData;
  286.  
  287.         if (cTimers == 1)
  288.         {
  289.             // only one timer:
  290.             // that's easy
  291.             #ifdef DEBUG_XTIMERS
  292.                 _Pmpf(("  got 1 timer"));
  293.             #endif
  294.  
  295.             pSet->ulPMTimeout = pTimer1->ulTimeout;
  296.         }
  297.         else if (cTimers == 2)
  298.         {
  299.             // exactly two timers:
  300.             // find the greatest common denominator
  301.             PXTIMER pTimer2 = (PXTIMER)pNode->pNext->pItemData;
  302.             #ifdef DEBUG_XTIMERS
  303.                 _Pmpf(("  got 2 timers"));
  304.             #endif
  305.  
  306.             pSet->ulPMTimeout = mathGCD(pTimer1->ulTimeout,
  307.                                         pTimer2->ulTimeout);
  308.         }
  309.         else
  310.         {
  311.             // more than two timers:
  312.             // run through all timers and find the greatest
  313.             // common denominator of all frequencies...
  314.             int     *paInts = (int*)_alloca(sizeof(int) * cTimers),
  315.                     i = 0;
  316.  
  317.             #ifdef DEBUG_XTIMERS
  318.                 _Pmpf(("  got %d timers", cTimers));
  319.             #endif
  320.  
  321.             // fill an array of integers with the
  322.             // timer frequencies
  323.             while (pNode)
  324.             {
  325.                 pTimer1 = (PXTIMER)pNode->pItemData;
  326.  
  327.                 #ifdef DEBUG_XTIMERS
  328.                     _Pmpf(("    timeout %d is %d", i, pTimer1->ulTimeout));
  329.                 #endif
  330.  
  331.                 paInts[i++] = pTimer1->ulTimeout;
  332.  
  333.                 pNode = pNode->pNext;
  334.             }
  335.  
  336.             pSet->ulPMTimeout = mathGCDMulti(paInts,
  337.                                              cTimers);
  338.         }
  339.  
  340.         #ifdef DEBUG_XTIMERS
  341.             _Pmpf(("--> GCD is %d", pSet->ulPMTimeout));
  342.         #endif
  343.  
  344.         if (    (!pSet->idPMTimerRunning)       // timer not running?
  345.              || (pSet->ulPMTimeout != ulOldPMTimeout) // timeout changed?
  346.            )
  347.         {
  348.             // start or restart PM timer
  349.             pSet->idPMTimerRunning = WinStartTimer(pSet->hab,
  350.                                                    pSet->hwndOwner,
  351.                                                    pSet->idPMTimer,
  352.                                                    pSet->ulPMTimeout);
  353.         }
  354.     }
  355. }
  356.  
  357. /* ******************************************************************
  358.  *
  359.  *   Exported functions
  360.  *
  361.  ********************************************************************/
  362.  
  363. /*
  364.  *@@ tmrCreateSet:
  365.  *      creates a "timer set" for use with the XTimer functions.
  366.  *      This is the first step if you want to use the XTimers.
  367.  *
  368.  *      hwndOwner must specify the "owner window", the target
  369.  *      window for the master PM timer. This window must respond
  370.  *      to a WM_TIMER message with the specified usPMTimerID and
  371.  *      invoke tmrTimerTick then.
  372.  *
  373.  *      Note that the master timer is not started until an XTimer
  374.  *      is started.
  375.  *
  376.  *      Use tmrDestroySet to free all resources again.
  377.  *
  378.  *@@added V0.9.9 (2001-02-28) [umoeller]
  379.  */
  380.  
  381. PXTIMERSET tmrCreateSet(HWND hwndOwner,         // in: owner window
  382.                         USHORT usPMTimerID)
  383. {
  384.     PXTIMERSET pSet = NEW(XTIMERSET);
  385.     if (pSet)
  386.     {
  387.         pSet->hab = WinQueryAnchorBlock(hwndOwner);
  388.         pSet->hwndOwner = hwndOwner;
  389.         pSet->idPMTimer = usPMTimerID;
  390.         pSet->idPMTimerRunning = 0;
  391.         pSet->ulPMTimeout = 0;
  392.  
  393.         pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
  394.     }
  395.  
  396.     return (pSet);
  397. }
  398.  
  399. /*
  400.  *@@ tmrDestroySet:
  401.  *      destroys a timer set previously created using
  402.  *      tmrCreateSet.
  403.  *
  404.  *      Of course, this will stop all XTimers which
  405.  *      might still be running with this set.
  406.  *
  407.  *@@added V0.9.9 (2001-02-28) [umoeller]
  408.  *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  409.  */
  410.  
  411. VOID tmrDestroySet(PXTIMERSET pSet)     // in: timer set (from tmrCreateSet)
  412. {
  413.     if (LockTimers())
  414.     {
  415.         if (pSet)
  416.         {
  417.             PLINKLIST pllXTimers;
  418.             if (pllXTimers = (PLINKLIST)pSet->pvllXTimers)
  419.             {
  420.                 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
  421.                 while (pTimerNode)
  422.                 {
  423.                     PLISTNODE pNext = pTimerNode->pNext;
  424.                     PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
  425.                     RemoveTimer(pSet, pTimer);
  426.                     pTimerNode = pNext;
  427.                 }
  428.  
  429.                 lstFree(&pllXTimers);
  430.                 pSet->pvllXTimers = NULL;
  431.             }
  432.  
  433.             if (pSet->idPMTimerRunning)
  434.             {
  435.                 WinStopTimer(pSet->hab,
  436.                              pSet->hwndOwner,
  437.                              pSet->idPMTimer);
  438.                 pSet->idPMTimerRunning = 0;
  439.             }
  440.  
  441.             free(pSet);
  442.         }
  443.  
  444.         UnlockTimers();
  445.     }
  446. }
  447.  
  448. /*
  449.  *@@ tmrTimerTick:
  450.  *      implements a PM timer tick.
  451.  *
  452.  *      When your window procs receives WM_TIMER for the
  453.  *      one PM timer which is supposed to trigger all the
  454.  *      XTimers, it must call this function. This will
  455.  *      evaluate all XTimers on the list and "fire" them
  456.  *      by calling the window procs directly with the
  457.  *      WM_TIMER message.
  458.  *
  459.  *@@added V0.9.9 (2001-02-28) [umoeller]
  460.  *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  461.  *@@changed V0.9.12 (2001-05-24) [umoeller]: fixed crash if this got called during tmrTimerTick
  462.  *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed mem overwrite which might have caused crashes if this got called during tmrTimerTick
  463.  *@@changed V0.9.14 (2001-08-03) [umoeller]: fixed "half frequency" regression caused by frequency optimizations
  464.  *@@changed V0.9.16 (2001-12-18) [umoeller]: now using WinDispatchMsg to avoid crashes during win destruction
  465.  *@@changed V0.9.19 (2002-05-04) [umoeller]: added excpt handling to avoid hanging all timers on the mutex
  466.  */
  467.  
  468. VOID tmrTimerTick(PXTIMERSET pSet)      // in: timer set (from tmrCreateSet)
  469. {
  470.     BOOL fLocked = FALSE;
  471.  
  472.     TRY_LOUD(excpt1)
  473.     {
  474.         if (fLocked = LockTimers())
  475.         {
  476.             PLINKLIST pllXTimers;
  477.             if (    (pSet)
  478.                  && (pllXTimers = (PLINKLIST)pSet->pvllXTimers)
  479.                )
  480.             {
  481.                 // go thru all XTimers and see which one
  482.                 // has elapsed; for all of these, post WM_TIMER
  483.                 // to the target window proc
  484.                 PLISTNODE pTimerNode;
  485.  
  486.                 if (!(pTimerNode = lstQueryFirstNode(pllXTimers)))
  487.                 {
  488.                     // no timers left:
  489.                     if (pSet->idPMTimerRunning)
  490.                     {
  491.                         // but PM timer running:
  492.                         // stop it
  493.                         WinStopTimer(pSet->hab,
  494.                                      pSet->hwndOwner,
  495.                                      pSet->idPMTimer);
  496.                         pSet->idPMTimerRunning = 0;
  497.                     }
  498.  
  499.                     pSet->ulPMTimeout = 0;
  500.                 }
  501.                 else
  502.                 {
  503.                     // we have timers:
  504.                     BOOL    fFoundInvalid = FALSE;
  505.  
  506.                     // get current time
  507.                     ULONG   ulTimeNow = 0;
  508.                     DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
  509.                                     &ulTimeNow, sizeof(ulTimeNow));
  510.  
  511.                     #ifdef DEBUG_XTIMERS
  512.                         _Pmpf((__FUNCTION__ ": ulTimeNow = %d", ulTimeNow));
  513.                     #endif
  514.  
  515.                     while (pTimerNode)
  516.                     {
  517.                         // get next node first because the
  518.                         // list can get modified while processing
  519.                         // V0.9.12 (2001-05-24) [umoeller]
  520.                         PLISTNODE pNext = pTimerNode->pNext;
  521.  
  522.                         PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
  523.  
  524.                         #ifdef DEBUG_XTIMERS
  525.                             _Pmpf(("   timer %d: ulNextFire = %d",
  526.                                     lstIndexFromItem(pllXTimers, pTimer),
  527.                                     pTimer->ulNextFire));
  528.                         #endif
  529.  
  530.                         if (    (pTimer)
  531.                              // && (pTimer->ulNextFire < ulTimeNow)
  532.                                 // V0.9.14 (2001-08-01) [umoeller]
  533.                                 // use <= because otherwise we'll get
  534.                                 // only half the frequency...
  535.                                 // we get here frequently where the
  536.                                 // two values are EXACTLY equal due
  537.                                 // to the above optimization (DosQuerySysInfo
  538.                                 // called once only for the entire loop)
  539.                              && (pTimer->ulNextFire <= ulTimeNow)
  540.                            )
  541.                         {
  542.                             // this timer has elapsed:
  543.                             // fire!
  544.  
  545.                             #ifdef DEBUG_XTIMERS
  546.                                 _Pmpf(("   --> fire!"));
  547.                             #endif
  548.  
  549.                             if (WinIsWindow(pSet->hab,
  550.                                             pTimer->hwndTarget))
  551.                             {
  552.                                 // window still valid:
  553.                                 // get the window's window proc
  554.                                 QMSG qmsg;
  555.                                 PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
  556.                                                                        QWP_PFNWP);
  557.  
  558.                                 // moved this up V0.9.14 (2001-08-01) [umoeller]
  559.                                 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
  560.  
  561.                                 // call the window proc DIRECTLY
  562.                                 // V0.9.16 (2001-12-18) [umoeller]:
  563.                                 // now using WinDispatchMsg to avoid crashes
  564.                                 // while hwndTarget is being destroyed
  565.                                 /* qmsg.hwnd = pTimer->hwndTarget;
  566.                                 qmsg.msg = WM_TIMER;
  567.                                 qmsg.mp1 = (MPARAM)pTimer->usTimerID;
  568.                                 qmsg.mp2 = (MPARAM)0;
  569.                                 qmsg.time = 0;
  570.                                 qmsg.ptl.x = 0;
  571.                                 qmsg.ptl.y = 0;
  572.                                 qmsg.reserved = 0;
  573.                                 WinDispatchMsg(pSet->hab,
  574.                                                &qmsg); */
  575.  
  576.                                 pfnwp(pTimer->hwndTarget,
  577.                                       WM_TIMER,
  578.                                       (MPARAM)pTimer->usTimerID,
  579.                                       0);
  580.                                     // V0.9.12 (2001-05-24) [umoeller]
  581.                                     // if the winproc chooses to start or
  582.                                     // stop a timer, pNext still points
  583.                                     // to a valid node...
  584.                                     // -- if a timer is removed, that's OK
  585.                                     // -- if a timer is added, it is added to
  586.                                     //    the list, so we'll see it in this loop
  587.  
  588.                                 // V0.9.14 (2001-08-01) [umoeller]
  589.  
  590.                                 // DO NOT REFERENCE pTimer AFTER THIS CODE;
  591.                                 // the winproc might have removed the timer,
  592.                                 // and since the list is auto-free, pTimer
  593.                                 // might have been freed!!
  594.                             }
  595.                             else
  596.                             {
  597.                                 // window has been destroyed:
  598.                                 lstRemoveNode(pllXTimers,
  599.                                               pTimerNode);
  600.                                     // pNext is still valid
  601.  
  602.                                 fFoundInvalid = TRUE;
  603.                             }
  604.  
  605.                         } // end if (pTimer->ulNextFire < ulTimeNow)
  606.  
  607.                         // next timer
  608.                         pTimerNode = pNext; // V0.9.12 (2001-05-24) [umoeller]
  609.                     } // end while (pTimerNode)
  610.  
  611.                     // destroy invalid timers, if any
  612.                     if (fFoundInvalid)
  613.                         AdjustPMTimer(pSet);
  614.  
  615.                 } // end else if (!pTimerNode)
  616.             } // end if (pllXTimers)
  617.         } // end if (LockTimers())
  618.     }
  619.     CATCH(excpt1) {} END_CATCH();
  620.  
  621.     if (fLocked)
  622.         UnlockTimers();
  623. }
  624.  
  625. /*
  626.  *@@ tmrStartXTimer:
  627.  *      starts an XTimer.
  628.  *
  629.  *      Any window can request an XTimer using
  630.  *      this function. This operates similar to
  631.  *      WinStartTimer, except that the number of
  632.  *      XTimers is not limited.
  633.  *
  634.  *      Returns the ID of a new timer or resets an
  635.  *      existing timer (if usTimerID is already used
  636.  *      with hwnd). Use tmrStopXTimer to stop the timer.
  637.  *
  638.  *      Returns 0 if an error occured. It is thus
  639.  *      invalid to specify a timer ID of 0.
  640.  *
  641.  *      The timer is _not_ stopped automatically
  642.  *      when the window is destroyed.
  643.  *
  644.  *      Note: Unless you own the timer set that
  645.  *      your timer runs on, it is strongly recommended
  646.  *      that your timer frequency is set to a multiple
  647.  *      of 125. The PM master timer behind the timer
  648.  *      set will be set to the greatest common divisor
  649.  *      of all frequencies, and if you set one timer
  650.  *      to 2000 and the other one to 2001, you will
  651.  *      cause quite a lot of overhead. This applies
  652.  *      especially to timers started by XCenter widgets.
  653.  *
  654.  *      For security, all timer frequencies will be
  655.  *      rounded to multiples of 25 anyway. Still,
  656.  *      running two timers at 1000 and 1025 will cause
  657.  *      the master timer to be set to 25, which is
  658.  *      overkill.
  659.  *
  660.  *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
  661.  *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  662.  *@@changed V0.9.14 (2001-07-12) [umoeller]: now rounding freq's to multiples of 25
  663.  */
  664.  
  665. USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
  666.                                HWND hwnd,       // in: target window for XTimer
  667.                                USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER (must be > 0)
  668.                                ULONG ulTimeout) // in: XTimer's timeout
  669. {
  670.     USHORT  usrc = 0;
  671.  
  672.     // _Pmpf((__FUNCTION__ ": entering"));
  673.  
  674.     if (LockTimers())
  675.     {
  676.         PLINKLIST pllXTimers;
  677.         if (    (pSet)
  678.              && (pllXTimers = (PLINKLIST)pSet->pvllXTimers)
  679.              && (hwnd)
  680.              && (ulTimeout)
  681.              && (usTimerID)     // V0.9.16 (2001-12-18) [umoeller]
  682.            )
  683.         {
  684.             PXTIMER pTimer;
  685.             ULONG ulTimeNow;
  686.  
  687.             // fix the timeout... we allow only multiples of
  688.             // 25, and it must be at least 25 (otherwise our
  689.             // internal master timer calculations will fail)
  690.             // V0.9.14 (2001-07-07) [umoeller]
  691.             if (ulTimeout < 25)
  692.                 ulTimeout = 25;
  693.             else
  694.                 ulTimeout = (ulTimeout + 10) / 25 * 25;
  695.  
  696.             DosQuerySysInfo(QSV_MS_COUNT,
  697.                             QSV_MS_COUNT,
  698.                             &ulTimeNow,
  699.                             sizeof(ulTimeNow));
  700.  
  701.             // check if this timer exists already
  702.             if (pTimer = FindTimer(pSet,
  703.                                    hwnd,
  704.                                    usTimerID))
  705.             {
  706.                 // exists already: reset only
  707.                 pTimer->ulNextFire = ulTimeNow + ulTimeout;
  708.                 usrc = usTimerID;
  709.             }
  710.             else
  711.             {
  712.                 // new timer needed:
  713.                 if (pTimer = NEW(XTIMER))
  714.                 {
  715.                     pTimer->usTimerID = usTimerID;
  716.                     pTimer->hwndTarget = hwnd;
  717.                     pTimer->ulTimeout = ulTimeout;
  718.                     pTimer->ulNextFire = ulTimeNow + ulTimeout;
  719.  
  720.                     lstAppendItem(pllXTimers,
  721.                                   pTimer);
  722.                     usrc = usTimerID;
  723.                 }
  724.             }
  725.  
  726.             if (usrc)
  727.                 // timer created or reset:
  728.                 AdjustPMTimer(pSet);
  729.  
  730.         } // if ((hwnd) && (ulTimeout))
  731.  
  732.         UnlockTimers();
  733.     }
  734.  
  735.     return (usrc);
  736. }
  737.  
  738. /*
  739.  *@@ tmrStopXTimer:
  740.  *      similar to WinStopTimer, this stops the
  741.  *      specified timer (which must have been
  742.  *      started with the same hwnd and usTimerID
  743.  *      using tmrStartXTimer).
  744.  *
  745.  *      Returns TRUE if the timer was stopped.
  746.  *
  747.  *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
  748.  */
  749.  
  750. BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet,    // in: timer set (from tmrCreateSet)
  751.                             HWND hwnd,
  752.                             USHORT usTimerID)
  753. {
  754.     BOOL brc = FALSE;
  755.     if (LockTimers())
  756.     {
  757.         PXTIMER pTimer;
  758.         #ifdef DEBUG_XTIMERS
  759.         _Pmpf((__FUNCTION__ ": finding timer %d", usTimerID));
  760.         #endif
  761.         if (pTimer = FindTimer(pSet,
  762.                                hwnd,
  763.                                usTimerID))
  764.                 // FindTimer checks the params
  765.         {
  766.             RemoveTimer(pSet, pTimer);
  767.             // recalculate
  768.             AdjustPMTimer(pSet);
  769.             brc = TRUE;
  770.         }
  771.  
  772.         UnlockTimers();
  773.     }
  774.  
  775.     return brc;
  776. }
  777.  
  778.  
  779.