set svn:eol-style to native
[reactos.git] / reactos / lib / winmm / time.c
index 4b5162e..a460914 100644 (file)
-/* -*- tab-width: 8; c-basic-offset: 4 -*- */\r
-\r
-/*\r
- * MMSYSTEM time functions\r
- *\r
- * Copyright 1993 Martin Ayotte\r
- *\r
- * This library is free software; you can redistribute it and/or\r
- * modify it under the terms of the GNU Lesser General Public\r
- * License as published by the Free Software Foundation; either\r
- * version 2.1 of the License, or (at your option) any later version.\r
- *\r
- * This library is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
- * Lesser General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU Lesser General Public\r
- * License along with this library; if not, write to the Free Software\r
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
- */\r
-\r
-#include "config.h"\r
-#include "wine/port.h"\r
-\r
-#include <stdarg.h>\r
-#include <time.h>\r
-#ifdef HAVE_SYS_TIME_H\r
-# include <sys/time.h>\r
-#endif\r
-#ifdef HAVE_UNISTD_H\r
-# include <unistd.h>\r
-#endif\r
-\r
-#include "windef.h"\r
-#include "winbase.h"\r
-#include "mmsystem.h"\r
-\r
-#include "winemm.h"\r
-\r
-#include "wine/debug.h"\r
-\r
-WINE_DEFAULT_DEBUG_CHANNEL(mmtime);\r
-\r
-static    HANDLE                TIME_hMMTimer;\r
-static    LPWINE_TIMERENTRY    TIME_TimersList;\r
-static    HANDLE                TIME_hKillEvent;\r
-static    HANDLE                TIME_hWakeEvent;\r
-static    BOOL                  TIME_TimeToDie = TRUE;\r
-\r
-/*\r
- * Some observations on the behavior of winmm on Windows.\r
- * First, the call to timeBeginPeriod(xx) can never be used\r
- * to raise the timer resolution, only lower it.\r
- *\r
- * Second, a brief survey of a variety of Win 2k and Win X\r
- * machines showed that a 'standard' (aka default) timer\r
- * resolution was 1 ms (Win9x is documented as being 1).  However, one \r
- * machine had a standard timer resolution of 10 ms.\r
- *\r
- * Further, if we set our default resolution to 1,\r
- * the implementation of timeGetTime becomes GetTickCount(),\r
- * and we can optimize the code to reduce overhead.\r
- *\r
- * Additionally, a survey of Event behaviors shows that\r
- * if we request a Periodic event every 50 ms, then Windows\r
- * makes sure to trigger that event 20 times in the next\r
- * second.  If delays prevent that from happening on exact\r
- * schedule, Windows will trigger the events as close\r
- * to the original schedule as is possible, and will eventually\r
- * bring the event triggers back onto a schedule that is\r
- * consistent with what would have happened if there were\r
- * no delays.\r
- *\r
- *   Jeremy White, October 2004\r
- */\r
-#define MMSYSTIME_MININTERVAL (1)\r
-#define MMSYSTIME_MAXINTERVAL (65535)\r
-\r
-\r
-static void    TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)\r
-{\r
-    TRACE("%04lx:CallBack => lpFunc=%p wTimerID=%04X dwUser=%08lX dwTriggerTime %ld(delta %ld)\n",\r
-         GetCurrentThreadId(), lpTimer->lpFunc, lpTimer->wTimerID, lpTimer->dwUser,\r
-          lpTimer->dwTriggerTime, GetTickCount() - lpTimer->dwTriggerTime);\r
-\r
-    /* - TimeProc callback that is called here is something strange, under Windows 3.1x it is called\r
-     *                 during interrupt time,  is allowed to execute very limited number of API calls (like\r
-     *         PostMessage), and must reside in DLL (therefore uses stack of active application). So I\r
-     *       guess current implementation via SetTimer has to be improved upon.\r
-     */\r
-    switch (lpTimer->wFlags & 0x30) {\r
-    case TIME_CALLBACK_FUNCTION:\r
-       if (lpTimer->wFlags & WINE_TIMER_IS32)\r
-           (lpTimer->lpFunc)(lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);\r
-       else if (pFnCallMMDrvFunc16)\r
-           pFnCallMMDrvFunc16((DWORD)lpTimer->lpFunc, lpTimer->wTimerID, 0,\r
-                               lpTimer->dwUser, 0, 0);\r
-       break;\r
-    case TIME_CALLBACK_EVENT_SET:\r
-       SetEvent((HANDLE)lpTimer->lpFunc);\r
-       break;\r
-    case TIME_CALLBACK_EVENT_PULSE:\r
-       PulseEvent((HANDLE)lpTimer->lpFunc);\r
-       break;\r
-    default:\r
-       FIXME("Unknown callback type 0x%04x for mmtime callback (%p), ignored.\n",\r
-             lpTimer->wFlags, lpTimer->lpFunc);\r
-       break;\r
-    }\r
-}\r
-\r
-/**************************************************************************\r
- *           TIME_MMSysTimeCallback\r
- */\r
-static DWORD CALLBACK TIME_MMSysTimeCallback(LPWINE_MM_IDATA iData)\r
-{\r
-static    int                          nSizeLpTimers;\r
-static    LPWINE_TIMERENTRY            lpTimers;\r
-\r
-    LPWINE_TIMERENTRY   timer, *ptimer, *next_ptimer;\r
-    int                        idx;\r
-    DWORD               cur_time;\r
-    DWORD               delta_time;\r
-    DWORD               ret_time = INFINITE;\r
-    DWORD               adjust_time;\r
-\r
-\r
-    /* optimize for the most frequent case  - no events */\r
-    if (! TIME_TimersList)\r
-        return(ret_time);\r
-\r
-    /* since timeSetEvent() and timeKillEvent() can be called\r
-     * from 16 bit code, there are cases where win16 lock is\r
-     * locked upon entering timeSetEvent(), and then the mm timer\r
-     * critical section is locked. This function cannot call the\r
-     * timer callback with the crit sect locked (because callback\r
-     * may need to acquire Win16 lock, thus providing a deadlock\r
-     * situation).\r
-     * To cope with that, we just copy the WINE_TIMERENTRY struct\r
-     * that need to trigger the callback, and call it without the\r
-     * mm timer crit sect locked.\r
-     * the hKillTimeEvent is used to mark the section where we \r
-     * handle the callbacks so we can do synchronous kills.\r
-     * EPP 99/07/13, updated 04/01/10\r
-     */\r
-    idx = 0;\r
-    cur_time = GetTickCount();\r
-\r
-    EnterCriticalSection(&iData->cs);\r
-    for (ptimer = &TIME_TimersList; *ptimer != NULL; ) {\r
-        timer = *ptimer;\r
-        next_ptimer = &timer->lpNext;\r
-        if (cur_time >= timer->dwTriggerTime)\r
-        {\r
-            if (timer->lpFunc) {\r
-                if (idx == nSizeLpTimers) {\r
-                    if (lpTimers) \r
-                        lpTimers = (LPWINE_TIMERENTRY)\r
-                            HeapReAlloc(GetProcessHeap(), 0, lpTimers,\r
-                                        ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));\r
-                    else \r
-                        lpTimers = (LPWINE_TIMERENTRY)\r
-                        HeapAlloc(GetProcessHeap(), 0,\r
-                                  ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));\r
-                }\r
-                lpTimers[idx++] = *timer;\r
-\r
-            }\r
-\r
-            /* Update the time after we make the copy to preserve\r
-               the original trigger time    */\r
-            timer->dwTriggerTime += timer->wDelay;\r
-\r
-            /* TIME_ONESHOT is defined as 0 */\r
-            if (!(timer->wFlags & TIME_PERIODIC))\r
-            {\r
-                /* unlink timer from timers list */\r
-                *ptimer = *next_ptimer;\r
-                HeapFree(GetProcessHeap(), 0, timer);\r
-\r
-                /* We don't need to trigger oneshots again */\r
-                delta_time = INFINITE;\r
-            }\r
-            else\r
-            {\r
-                /* Compute when this event needs this function\r
-                    to be called again */\r
-                if (timer->dwTriggerTime <= cur_time)\r
-                    delta_time = 0;\r
-                else\r
-                    delta_time = timer->dwTriggerTime - cur_time;\r
-            }\r
-        } \r
-        else\r
-            delta_time = timer->dwTriggerTime - cur_time;\r
-\r
-        /* Determine when we need to return to this function */\r
-        ret_time = min(ret_time, delta_time);\r
-\r
-        ptimer = next_ptimer;\r
-    }\r
-    if (TIME_hKillEvent) ResetEvent(TIME_hKillEvent);\r
-    LeaveCriticalSection(&iData->cs);\r
-\r
-    while (idx > 0) TIME_TriggerCallBack(&lpTimers[--idx]);\r
-    if (TIME_hKillEvent) SetEvent(TIME_hKillEvent);\r
-\r
-    /* Finally, adjust the recommended wait time downward\r
-       by the amount of time the processing routines \r
-       actually took */\r
-    adjust_time = GetTickCount() - cur_time;\r
-    if (adjust_time > ret_time)\r
-        ret_time = 0;\r
-    else\r
-        ret_time -= adjust_time;\r
-\r
-    /* We return the amount of time our caller should sleep\r
-       before needing to check in on us again       */\r
-    return(ret_time);\r
-}\r
-\r
-/**************************************************************************\r
- *           TIME_MMSysTimeThread\r
- */\r
-static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)\r
-{\r
-    LPWINE_MM_IDATA iData = (LPWINE_MM_IDATA)arg;\r
-    DWORD sleep_time;\r
-    DWORD rc;\r
-\r
-    TRACE("Starting main winmm thread\n");\r
-\r
-    /* FIXME:  As an optimization, we could have\r
-               this thread die when there are no more requests\r
-               pending, and then get recreated on the first\r
-               new event; it's not clear if that would be worth\r
-               it or not.                 */\r
-\r
-    while (! TIME_TimeToDie) \r
-    {\r
-       sleep_time = TIME_MMSysTimeCallback(iData);\r
-\r
-        if (sleep_time == 0)\r
-            continue;\r
-\r
-        rc = WaitForSingleObject(TIME_hWakeEvent, sleep_time);\r
-        if (rc != WAIT_TIMEOUT && rc != WAIT_OBJECT_0)\r
-        {   \r
-            FIXME("Unexpected error %ld(%ld) in timer thread\n", rc, GetLastError());\r
-            break;\r
-        }\r
-    }\r
-    TRACE("Exiting main winmm thread\n");\r
-    return 0;\r
-}\r
-\r
-/**************************************************************************\r
- *                             TIME_MMTimeStart\r
- */\r
-void   TIME_MMTimeStart(void)\r
-{\r
-    if (!TIME_hMMTimer) {\r
-       TIME_TimersList = NULL;\r
-        TIME_hWakeEvent = CreateEventW(NULL, FALSE, FALSE, NULL);\r
-        TIME_TimeToDie = FALSE;\r
-       TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, &WINMM_IData, 0, NULL);\r
-        SetThreadPriority(TIME_hMMTimer, THREAD_PRIORITY_TIME_CRITICAL);\r
-    }\r
-}\r
-\r
-/**************************************************************************\r
- *                             TIME_MMTimeStop\r
- */\r
-void   TIME_MMTimeStop(void)\r
-{\r
-    if (TIME_hMMTimer) {\r
-\r
-        TIME_TimeToDie = TRUE;\r
-        SetEvent(TIME_hWakeEvent);\r
-\r
-        /* FIXME: in the worst case, we're going to wait 65 seconds here :-( */\r
-       WaitForSingleObject(TIME_hMMTimer, INFINITE);\r
-\r
-       CloseHandle(TIME_hMMTimer);\r
-       CloseHandle(TIME_hWakeEvent);\r
-       TIME_hMMTimer = 0;\r
-        TIME_TimersList = NULL;\r
-    }\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeGetSystemTime       [WINMM.@]\r
- */\r
-MMRESULT WINAPI timeGetSystemTime(LPMMTIME lpTime, UINT wSize)\r
-{\r
-\r
-    if (wSize >= sizeof(*lpTime)) {\r
-       lpTime->wType = TIME_MS;\r
-       lpTime->u.ms = GetTickCount();\r
-\r
-    }\r
-\r
-    return 0;\r
-}\r
-\r
-/**************************************************************************\r
- *                             TIME_SetEventInternal   [internal]\r
- */\r
-WORD   TIME_SetEventInternal(UINT wDelay, UINT wResol,\r
-                              LPTIMECALLBACK lpFunc, DWORD dwUser, UINT wFlags)\r
-{\r
-    WORD               wNewID = 0;\r
-    LPWINE_TIMERENTRY  lpNewTimer;\r
-    LPWINE_TIMERENTRY  lpTimer;\r
-\r
-    TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);\r
-\r
-    if (wDelay < MMSYSTIME_MININTERVAL || wDelay > MMSYSTIME_MAXINTERVAL)\r
-       return 0;\r
-\r
-    lpNewTimer = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_TIMERENTRY));\r
-    if (lpNewTimer == NULL)\r
-       return 0;\r
-\r
-    TIME_MMTimeStart();\r
-\r
-    lpNewTimer->wDelay = wDelay;\r
-    lpNewTimer->dwTriggerTime = GetTickCount() + wDelay;\r
-\r
-    /* FIXME - wResol is not respected, although it is not clear\r
-               that we could change our precision meaningfully  */\r
-    lpNewTimer->wResol = wResol;\r
-    lpNewTimer->lpFunc = lpFunc;\r
-    lpNewTimer->dwUser = dwUser;\r
-    lpNewTimer->wFlags = wFlags;\r
-\r
-    EnterCriticalSection(&WINMM_IData.cs);\r
-\r
-    if ((wFlags & TIME_KILL_SYNCHRONOUS) && !TIME_hKillEvent)\r
-        TIME_hKillEvent = CreateEventW(NULL, TRUE, TRUE, NULL);\r
-\r
-    for (lpTimer = TIME_TimersList; lpTimer != NULL; lpTimer = lpTimer->lpNext) {\r
-       wNewID = max(wNewID, lpTimer->wTimerID);\r
-    }\r
-\r
-    lpNewTimer->lpNext = TIME_TimersList;\r
-    TIME_TimersList = lpNewTimer;\r
-    lpNewTimer->wTimerID = wNewID + 1;\r
-\r
-    LeaveCriticalSection(&WINMM_IData.cs);\r
-\r
-    /* Wake the service thread in case there is work to be done */\r
-    SetEvent(TIME_hWakeEvent);\r
-\r
-    TRACE("=> %u\n", wNewID + 1);\r
-\r
-    return wNewID + 1;\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeSetEvent            [WINMM.@]\r
- */\r
-MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,\r
-                            DWORD_PTR dwUser, UINT wFlags)\r
-{\r
-    if (wFlags & WINE_TIMER_IS32)\r
-       WARN("Unknown windows flag... wine internally used.. ooch\n");\r
-\r
-    return TIME_SetEventInternal(wDelay, wResol, lpFunc,\r
-                                 dwUser, wFlags|WINE_TIMER_IS32);\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeKillEvent           [WINMM.@]\r
- */\r
-MMRESULT WINAPI timeKillEvent(UINT wID)\r
-{\r
-    LPWINE_TIMERENTRY   lpSelf = NULL, *lpTimer;\r
-\r
-    TRACE("(%u)\n", wID);\r
-    EnterCriticalSection(&WINMM_IData.cs);\r
-    /* remove WINE_TIMERENTRY from list */\r
-    for (lpTimer = &TIME_TimersList; *lpTimer; lpTimer = &(*lpTimer)->lpNext) {\r
-       if (wID == (*lpTimer)->wTimerID) {\r
-            lpSelf = *lpTimer;\r
-            /* unlink timer of id 'wID' */\r
-            *lpTimer = (*lpTimer)->lpNext;\r
-           break;\r
-       }\r
-    }\r
-    LeaveCriticalSection(&WINMM_IData.cs);\r
-\r
-    if (!lpSelf)\r
-    {\r
-        WARN("wID=%u is not a valid timer ID\n", wID);\r
-        return MMSYSERR_INVALPARAM;\r
-    }\r
-    if (lpSelf->wFlags & TIME_KILL_SYNCHRONOUS)\r
-        WaitForSingleObject(TIME_hKillEvent, INFINITE);\r
-    HeapFree(GetProcessHeap(), 0, lpSelf);\r
-    return TIMERR_NOERROR;\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeGetDevCaps          [WINMM.@]\r
- */\r
-MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS lpCaps, UINT wSize)\r
-{\r
-    TRACE("(%p, %u)\n", lpCaps, wSize);\r
-\r
-    if (lpCaps == 0) {\r
-        WARN("invalid lpCaps\n");\r
-        return TIMERR_NOCANDO;\r
-    }\r
-\r
-    if (wSize < sizeof(TIMECAPS)) {\r
-        WARN("invalid wSize\n");\r
-        return TIMERR_NOCANDO;\r
-    }\r
-\r
-    lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;\r
-    lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;\r
-    return TIMERR_NOERROR;\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeBeginPeriod         [WINMM.@]\r
- */\r
-MMRESULT WINAPI timeBeginPeriod(UINT wPeriod)\r
-{\r
-    if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)\r
-       return TIMERR_NOCANDO;\r
-\r
-    if (wPeriod > MMSYSTIME_MININTERVAL)\r
-    {\r
-        WARN("Stub; we set our timer resolution at minimum\n");\r
-    }\r
-\r
-    return 0;\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeEndPeriod           [WINMM.@]\r
- */\r
-MMRESULT WINAPI timeEndPeriod(UINT wPeriod)\r
-{\r
-    if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)\r
-       return TIMERR_NOCANDO;\r
-\r
-    if (wPeriod > MMSYSTIME_MININTERVAL)\r
-    {\r
-        WARN("Stub; we set our timer resolution at minimum\n");\r
-    }\r
-    return 0;\r
-}\r
-\r
-/**************************************************************************\r
- *                             timeGetTime    [MMSYSTEM.607]\r
- *                             timeGetTime    [WINMM.@]\r
- */\r
-DWORD WINAPI timeGetTime(void)\r
-{\r
-#if defined(COMMENTOUTPRIORTODELETING)\r
-    DWORD       count;\r
-\r
-    /* FIXME: releasing the win16 lock here is a temporary hack (I hope)\r
-     * that lets mciavi.drv run correctly\r
-     */\r
-    if (pFnReleaseThunkLock) pFnReleaseThunkLock(&count);\r
-    if (pFnRestoreThunkLock) pFnRestoreThunkLock(count);\r
-#endif\r
-\r
-    return GetTickCount();\r
-}\r
+/* -*- tab-width: 8; c-basic-offset: 4 -*- */
+
+/*
+ * MMSYSTEM time functions
+ *
+ * Copyright 1993 Martin Ayotte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "config.h"
+#include "wine/port.h"
+
+#include <stdarg.h>
+#include <time.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "windef.h"
+#include "winbase.h"
+#include "mmsystem.h"
+
+#include "winemm.h"
+
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(mmtime);
+
+static    HANDLE                TIME_hMMTimer;
+static    LPWINE_TIMERENTRY    TIME_TimersList;
+static    HANDLE                TIME_hKillEvent;
+static    HANDLE                TIME_hWakeEvent;
+static    BOOL                  TIME_TimeToDie = TRUE;
+
+/*
+ * Some observations on the behavior of winmm on Windows.
+ * First, the call to timeBeginPeriod(xx) can never be used
+ * to raise the timer resolution, only lower it.
+ *
+ * Second, a brief survey of a variety of Win 2k and Win X
+ * machines showed that a 'standard' (aka default) timer
+ * resolution was 1 ms (Win9x is documented as being 1).  However, one 
+ * machine had a standard timer resolution of 10 ms.
+ *
+ * Further, if we set our default resolution to 1,
+ * the implementation of timeGetTime becomes GetTickCount(),
+ * and we can optimize the code to reduce overhead.
+ *
+ * Additionally, a survey of Event behaviors shows that
+ * if we request a Periodic event every 50 ms, then Windows
+ * makes sure to trigger that event 20 times in the next
+ * second.  If delays prevent that from happening on exact
+ * schedule, Windows will trigger the events as close
+ * to the original schedule as is possible, and will eventually
+ * bring the event triggers back onto a schedule that is
+ * consistent with what would have happened if there were
+ * no delays.
+ *
+ *   Jeremy White, October 2004
+ */
+#define MMSYSTIME_MININTERVAL (1)
+#define MMSYSTIME_MAXINTERVAL (65535)
+
+
+static void    TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
+{
+    TRACE("%04lx:CallBack => lpFunc=%p wTimerID=%04X dwUser=%08lX dwTriggerTime %ld(delta %ld)\n",
+         GetCurrentThreadId(), lpTimer->lpFunc, lpTimer->wTimerID, lpTimer->dwUser,
+          lpTimer->dwTriggerTime, GetTickCount() - lpTimer->dwTriggerTime);
+
+    /* - TimeProc callback that is called here is something strange, under Windows 3.1x it is called
+     *                 during interrupt time,  is allowed to execute very limited number of API calls (like
+     *         PostMessage), and must reside in DLL (therefore uses stack of active application). So I
+     *       guess current implementation via SetTimer has to be improved upon.
+     */
+    switch (lpTimer->wFlags & 0x30) {
+    case TIME_CALLBACK_FUNCTION:
+       if (lpTimer->wFlags & WINE_TIMER_IS32)
+           (lpTimer->lpFunc)(lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);
+       else if (pFnCallMMDrvFunc16)
+           pFnCallMMDrvFunc16((DWORD)lpTimer->lpFunc, lpTimer->wTimerID, 0,
+                               lpTimer->dwUser, 0, 0);
+       break;
+    case TIME_CALLBACK_EVENT_SET:
+       SetEvent((HANDLE)lpTimer->lpFunc);
+       break;
+    case TIME_CALLBACK_EVENT_PULSE:
+       PulseEvent((HANDLE)lpTimer->lpFunc);
+       break;
+    default:
+       FIXME("Unknown callback type 0x%04x for mmtime callback (%p), ignored.\n",
+             lpTimer->wFlags, lpTimer->lpFunc);
+       break;
+    }
+}
+
+/**************************************************************************
+ *           TIME_MMSysTimeCallback
+ */
+static DWORD CALLBACK TIME_MMSysTimeCallback(LPWINE_MM_IDATA iData)
+{
+static    int                          nSizeLpTimers;
+static    LPWINE_TIMERENTRY            lpTimers;
+
+    LPWINE_TIMERENTRY   timer, *ptimer, *next_ptimer;
+    int                        idx;
+    DWORD               cur_time;
+    DWORD               delta_time;
+    DWORD               ret_time = INFINITE;
+    DWORD               adjust_time;
+
+
+    /* optimize for the most frequent case  - no events */
+    if (! TIME_TimersList)
+        return(ret_time);
+
+    /* since timeSetEvent() and timeKillEvent() can be called
+     * from 16 bit code, there are cases where win16 lock is
+     * locked upon entering timeSetEvent(), and then the mm timer
+     * critical section is locked. This function cannot call the
+     * timer callback with the crit sect locked (because callback
+     * may need to acquire Win16 lock, thus providing a deadlock
+     * situation).
+     * To cope with that, we just copy the WINE_TIMERENTRY struct
+     * that need to trigger the callback, and call it without the
+     * mm timer crit sect locked.
+     * the hKillTimeEvent is used to mark the section where we 
+     * handle the callbacks so we can do synchronous kills.
+     * EPP 99/07/13, updated 04/01/10
+     */
+    idx = 0;
+    cur_time = GetTickCount();
+
+    EnterCriticalSection(&iData->cs);
+    for (ptimer = &TIME_TimersList; *ptimer != NULL; ) {
+        timer = *ptimer;
+        next_ptimer = &timer->lpNext;
+        if (cur_time >= timer->dwTriggerTime)
+        {
+            if (timer->lpFunc) {
+                if (idx == nSizeLpTimers) {
+                    if (lpTimers) 
+                        lpTimers = (LPWINE_TIMERENTRY)
+                            HeapReAlloc(GetProcessHeap(), 0, lpTimers,
+                                        ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
+                    else 
+                        lpTimers = (LPWINE_TIMERENTRY)
+                        HeapAlloc(GetProcessHeap(), 0,
+                                  ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
+                }
+                lpTimers[idx++] = *timer;
+
+            }
+
+            /* Update the time after we make the copy to preserve
+               the original trigger time    */
+            timer->dwTriggerTime += timer->wDelay;
+
+            /* TIME_ONESHOT is defined as 0 */
+            if (!(timer->wFlags & TIME_PERIODIC))
+            {
+                /* unlink timer from timers list */
+                *ptimer = *next_ptimer;
+                HeapFree(GetProcessHeap(), 0, timer);
+
+                /* We don't need to trigger oneshots again */
+                delta_time = INFINITE;
+            }
+            else
+            {
+                /* Compute when this event needs this function
+                    to be called again */
+                if (timer->dwTriggerTime <= cur_time)
+                    delta_time = 0;
+                else
+                    delta_time = timer->dwTriggerTime - cur_time;
+            }
+        } 
+        else
+            delta_time = timer->dwTriggerTime - cur_time;
+
+        /* Determine when we need to return to this function */
+        ret_time = min(ret_time, delta_time);
+
+        ptimer = next_ptimer;
+    }
+    if (TIME_hKillEvent) ResetEvent(TIME_hKillEvent);
+    LeaveCriticalSection(&iData->cs);
+
+    while (idx > 0) TIME_TriggerCallBack(&lpTimers[--idx]);
+    if (TIME_hKillEvent) SetEvent(TIME_hKillEvent);
+
+    /* Finally, adjust the recommended wait time downward
+       by the amount of time the processing routines 
+       actually took */
+    adjust_time = GetTickCount() - cur_time;
+    if (adjust_time > ret_time)
+        ret_time = 0;
+    else
+        ret_time -= adjust_time;
+
+    /* We return the amount of time our caller should sleep
+       before needing to check in on us again       */
+    return(ret_time);
+}
+
+/**************************************************************************
+ *           TIME_MMSysTimeThread
+ */
+static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
+{
+    LPWINE_MM_IDATA iData = (LPWINE_MM_IDATA)arg;
+    DWORD sleep_time;
+    DWORD rc;
+
+    TRACE("Starting main winmm thread\n");
+
+    /* FIXME:  As an optimization, we could have
+               this thread die when there are no more requests
+               pending, and then get recreated on the first
+               new event; it's not clear if that would be worth
+               it or not.                 */
+
+    while (! TIME_TimeToDie) 
+    {
+       sleep_time = TIME_MMSysTimeCallback(iData);
+
+        if (sleep_time == 0)
+            continue;
+
+        rc = WaitForSingleObject(TIME_hWakeEvent, sleep_time);
+        if (rc != WAIT_TIMEOUT && rc != WAIT_OBJECT_0)
+        {   
+            FIXME("Unexpected error %ld(%ld) in timer thread\n", rc, GetLastError());
+            break;
+        }
+    }
+    TRACE("Exiting main winmm thread\n");
+    return 0;
+}
+
+/**************************************************************************
+ *                             TIME_MMTimeStart
+ */
+void   TIME_MMTimeStart(void)
+{
+    if (!TIME_hMMTimer) {
+       TIME_TimersList = NULL;
+        TIME_hWakeEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+        TIME_TimeToDie = FALSE;
+       TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, &WINMM_IData, 0, NULL);
+        SetThreadPriority(TIME_hMMTimer, THREAD_PRIORITY_TIME_CRITICAL);
+    }
+}
+
+/**************************************************************************
+ *                             TIME_MMTimeStop
+ */
+void   TIME_MMTimeStop(void)
+{
+    if (TIME_hMMTimer) {
+
+        TIME_TimeToDie = TRUE;
+        SetEvent(TIME_hWakeEvent);
+
+        /* FIXME: in the worst case, we're going to wait 65 seconds here :-( */
+       WaitForSingleObject(TIME_hMMTimer, INFINITE);
+
+       CloseHandle(TIME_hMMTimer);
+       CloseHandle(TIME_hWakeEvent);
+       TIME_hMMTimer = 0;
+        TIME_TimersList = NULL;
+    }
+}
+
+/**************************************************************************
+ *                             timeGetSystemTime       [WINMM.@]
+ */
+MMRESULT WINAPI timeGetSystemTime(LPMMTIME lpTime, UINT wSize)
+{
+
+    if (wSize >= sizeof(*lpTime)) {
+       lpTime->wType = TIME_MS;
+       lpTime->u.ms = GetTickCount();
+
+    }
+
+    return 0;
+}
+
+/**************************************************************************
+ *                             TIME_SetEventInternal   [internal]
+ */
+WORD   TIME_SetEventInternal(UINT wDelay, UINT wResol,
+                              LPTIMECALLBACK lpFunc, DWORD dwUser, UINT wFlags)
+{
+    WORD               wNewID = 0;
+    LPWINE_TIMERENTRY  lpNewTimer;
+    LPWINE_TIMERENTRY  lpTimer;
+
+    TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);
+
+    if (wDelay < MMSYSTIME_MININTERVAL || wDelay > MMSYSTIME_MAXINTERVAL)
+       return 0;
+
+    lpNewTimer = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_TIMERENTRY));
+    if (lpNewTimer == NULL)
+       return 0;
+
+    TIME_MMTimeStart();
+
+    lpNewTimer->wDelay = wDelay;
+    lpNewTimer->dwTriggerTime = GetTickCount() + wDelay;
+
+    /* FIXME - wResol is not respected, although it is not clear
+               that we could change our precision meaningfully  */
+    lpNewTimer->wResol = wResol;
+    lpNewTimer->lpFunc = lpFunc;
+    lpNewTimer->dwUser = dwUser;
+    lpNewTimer->wFlags = wFlags;
+
+    EnterCriticalSection(&WINMM_IData.cs);
+
+    if ((wFlags & TIME_KILL_SYNCHRONOUS) && !TIME_hKillEvent)
+        TIME_hKillEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
+
+    for (lpTimer = TIME_TimersList; lpTimer != NULL; lpTimer = lpTimer->lpNext) {
+       wNewID = max(wNewID, lpTimer->wTimerID);
+    }
+
+    lpNewTimer->lpNext = TIME_TimersList;
+    TIME_TimersList = lpNewTimer;
+    lpNewTimer->wTimerID = wNewID + 1;
+
+    LeaveCriticalSection(&WINMM_IData.cs);
+
+    /* Wake the service thread in case there is work to be done */
+    SetEvent(TIME_hWakeEvent);
+
+    TRACE("=> %u\n", wNewID + 1);
+
+    return wNewID + 1;
+}
+
+/**************************************************************************
+ *                             timeSetEvent            [WINMM.@]
+ */
+MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,
+                            DWORD_PTR dwUser, UINT wFlags)
+{
+    if (wFlags & WINE_TIMER_IS32)
+       WARN("Unknown windows flag... wine internally used.. ooch\n");
+
+    return TIME_SetEventInternal(wDelay, wResol, lpFunc,
+                                 dwUser, wFlags|WINE_TIMER_IS32);
+}
+
+/**************************************************************************
+ *                             timeKillEvent           [WINMM.@]
+ */
+MMRESULT WINAPI timeKillEvent(UINT wID)
+{
+    LPWINE_TIMERENTRY   lpSelf = NULL, *lpTimer;
+
+    TRACE("(%u)\n", wID);
+    EnterCriticalSection(&WINMM_IData.cs);
+    /* remove WINE_TIMERENTRY from list */
+    for (lpTimer = &TIME_TimersList; *lpTimer; lpTimer = &(*lpTimer)->lpNext) {
+       if (wID == (*lpTimer)->wTimerID) {
+            lpSelf = *lpTimer;
+            /* unlink timer of id 'wID' */
+            *lpTimer = (*lpTimer)->lpNext;
+           break;
+       }
+    }
+    LeaveCriticalSection(&WINMM_IData.cs);
+
+    if (!lpSelf)
+    {
+        WARN("wID=%u is not a valid timer ID\n", wID);
+        return MMSYSERR_INVALPARAM;
+    }
+    if (lpSelf->wFlags & TIME_KILL_SYNCHRONOUS)
+        WaitForSingleObject(TIME_hKillEvent, INFINITE);
+    HeapFree(GetProcessHeap(), 0, lpSelf);
+    return TIMERR_NOERROR;
+}
+
+/**************************************************************************
+ *                             timeGetDevCaps          [WINMM.@]
+ */
+MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS lpCaps, UINT wSize)
+{
+    TRACE("(%p, %u)\n", lpCaps, wSize);
+
+    if (lpCaps == 0) {
+        WARN("invalid lpCaps\n");
+        return TIMERR_NOCANDO;
+    }
+
+    if (wSize < sizeof(TIMECAPS)) {
+        WARN("invalid wSize\n");
+        return TIMERR_NOCANDO;
+    }
+
+    lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;
+    lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;
+    return TIMERR_NOERROR;
+}
+
+/**************************************************************************
+ *                             timeBeginPeriod         [WINMM.@]
+ */
+MMRESULT WINAPI timeBeginPeriod(UINT wPeriod)
+{
+    if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
+       return TIMERR_NOCANDO;
+
+    if (wPeriod > MMSYSTIME_MININTERVAL)
+    {
+        WARN("Stub; we set our timer resolution at minimum\n");
+    }
+
+    return 0;
+}
+
+/**************************************************************************
+ *                             timeEndPeriod           [WINMM.@]
+ */
+MMRESULT WINAPI timeEndPeriod(UINT wPeriod)
+{
+    if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
+       return TIMERR_NOCANDO;
+
+    if (wPeriod > MMSYSTIME_MININTERVAL)
+    {
+        WARN("Stub; we set our timer resolution at minimum\n");
+    }
+    return 0;
+}
+
+/**************************************************************************
+ *                             timeGetTime    [MMSYSTEM.607]
+ *                             timeGetTime    [WINMM.@]
+ */
+DWORD WINAPI timeGetTime(void)
+{
+#if defined(COMMENTOUTPRIORTODELETING)
+    DWORD       count;
+
+    /* FIXME: releasing the win16 lock here is a temporary hack (I hope)
+     * that lets mciavi.drv run correctly
+     */
+    if (pFnReleaseThunkLock) pFnReleaseThunkLock(&count);
+    if (pFnRestoreThunkLock) pFnRestoreThunkLock(count);
+#endif
+
+    return GetTickCount();
+}