1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
4 * MMSYSTEM time functions
6 * Copyright 1993 Martin Ayotte
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include "wine/port.h"
28 #ifdef HAVE_SYS_TIME_H
29 # include <sys/time.h>
41 #include "wine/debug.h"
43 WINE_DEFAULT_DEBUG_CHANNEL(mmtime);
45 static HANDLE TIME_hMMTimer;
46 static LPWINE_TIMERENTRY TIME_TimersList;
47 static HANDLE TIME_hKillEvent;
48 DWORD WINMM_SysTimeMS;
52 * We're using "1" as the mininum resolution to the timer,
53 * as Windows 95 does, according to the docs. Maybe it should
54 * depend on the computers resources!
56 #define MMSYSTIME_MININTERVAL (1)
57 #define MMSYSTIME_MAXINTERVAL (65535)
59 #define MMSYSTIME_STDINTERVAL (10) /* reasonable value? */
61 static void TIME_TriggerCallBack(LPWINE_TIMERENTRY lpTimer)
63 TRACE("before CallBack => lpFunc=%p wTimerID=%04X dwUser=%08lX !\n",
64 lpTimer->lpFunc, lpTimer->wTimerID, lpTimer->dwUser);
66 /* - TimeProc callback that is called here is something strange, under Windows 3.1x it is called
67 * during interrupt time, is allowed to execute very limited number of API calls (like
68 * PostMessage), and must reside in DLL (therefore uses stack of active application). So I
69 * guess current implementation via SetTimer has to be improved upon.
71 switch (lpTimer->wFlags & 0x30) {
72 case TIME_CALLBACK_FUNCTION:
73 if (lpTimer->wFlags & WINE_TIMER_IS32)
74 (lpTimer->lpFunc)(lpTimer->wTimerID, 0, lpTimer->dwUser, 0, 0);
75 else if (pFnCallMMDrvFunc16)
76 pFnCallMMDrvFunc16((DWORD)lpTimer->lpFunc, lpTimer->wTimerID, 0,
77 lpTimer->dwUser, 0, 0);
79 case TIME_CALLBACK_EVENT_SET:
80 SetEvent((HANDLE)lpTimer->lpFunc);
82 case TIME_CALLBACK_EVENT_PULSE:
83 PulseEvent((HANDLE)lpTimer->lpFunc);
86 FIXME("Unknown callback type 0x%04x for mmtime callback (%p), ignored.\n",
87 lpTimer->wFlags, lpTimer->lpFunc);
90 TRACE("after CallBack !\n");
93 /**************************************************************************
94 * TIME_MMSysTimeCallback
96 static void CALLBACK TIME_MMSysTimeCallback(LPWINE_MM_IDATA iData)
98 static int nSizeLpTimers;
99 static LPWINE_TIMERENTRY lpTimers;
101 LPWINE_TIMERENTRY timer, *ptimer, *next_ptimer;
102 DWORD delta = GetTickCount() - WINMM_SysTimeMS;
105 TRACE("Time delta: %ld\n", delta);
107 while (delta >= MMSYSTIME_MININTERVAL) {
108 delta -= MMSYSTIME_MININTERVAL;
109 WINMM_SysTimeMS += MMSYSTIME_MININTERVAL;
111 /* since timeSetEvent() and timeKillEvent() can be called
112 * from 16 bit code, there are cases where win16 lock is
113 * locked upon entering timeSetEvent(), and then the mm timer
114 * critical section is locked. This function cannot call the
115 * timer callback with the crit sect locked (because callback
116 * may need to acquire Win16 lock, thus providing a deadlock
118 * To cope with that, we just copy the WINE_TIMERENTRY struct
119 * that need to trigger the callback, and call it without the
120 * mm timer crit sect locked.
121 * the hKillTimeEvent is used to mark the section where we
122 * handle the callbacks so we can do synchronous kills.
123 * EPP 99/07/13, updated 04/01/10
127 EnterCriticalSection(&iData->cs);
128 for (ptimer = &TIME_TimersList; *ptimer != NULL; ) {
130 next_ptimer = &timer->lpNext;
131 if (timer->uCurTime < MMSYSTIME_MININTERVAL) {
132 /* since lpTimer->wDelay is >= MININTERVAL, wCurTime value
133 * shall be correct (>= 0)
135 timer->uCurTime += timer->wDelay - MMSYSTIME_MININTERVAL;
137 if (idx == nSizeLpTimers) {
139 lpTimers = (LPWINE_TIMERENTRY)
140 HeapReAlloc(GetProcessHeap(), 0, lpTimers,
141 ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
143 lpTimers = (LPWINE_TIMERENTRY)
144 HeapAlloc(GetProcessHeap(), 0,
145 ++nSizeLpTimers * sizeof(WINE_TIMERENTRY));
147 lpTimers[idx++] = *timer;
149 /* TIME_ONESHOT is defined as 0 */
150 if (!(timer->wFlags & TIME_PERIODIC))
152 /* unlink timer from timers list */
153 *ptimer = *next_ptimer;
154 HeapFree(GetProcessHeap(), 0, timer);
157 timer->uCurTime -= MMSYSTIME_MININTERVAL;
159 ptimer = next_ptimer;
161 if (TIME_hKillEvent) ResetEvent(TIME_hKillEvent);
162 LeaveCriticalSection(&iData->cs);
164 while (idx > 0) TIME_TriggerCallBack(&lpTimers[--idx]);
165 if (TIME_hKillEvent) SetEvent(TIME_hKillEvent);
169 /**************************************************************************
170 * TIME_MMSysTimeThread
172 static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg)
174 LPWINE_MM_IDATA iData = (LPWINE_MM_IDATA)arg;
175 volatile HANDLE *pActive = (volatile HANDLE *)&TIME_hMMTimer;
176 DWORD last_time, cur_time;
178 usleep(MMSYSTIME_STDINTERVAL * 1000);
179 last_time = GetTickCount();
181 TIME_MMSysTimeCallback(iData);
182 cur_time = GetTickCount();
183 while (last_time < cur_time)
184 last_time += MMSYSTIME_STDINTERVAL;
185 usleep((last_time - cur_time) * 1000);
190 /**************************************************************************
193 void TIME_MMTimeStart(void)
195 /* one could think it's possible to stop the service thread activity when no more
196 * mm timers are active, but this would require to keep mmSysTimeMS up-to-date
197 * without being incremented within the service thread callback.
199 if (!TIME_hMMTimer) {
200 WINMM_SysTimeMS = GetTickCount();
201 TIME_TimersList = NULL;
202 TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, WINMM_IData, 0, NULL);
206 /**************************************************************************
209 void TIME_MMTimeStop(void)
211 /* FIXME: in the worst case, we're going to wait 65 seconds here :-( */
213 HANDLE hMMTimer = TIME_hMMTimer;
215 WaitForSingleObject(hMMTimer, INFINITE);
216 CloseHandle(hMMTimer);
220 /**************************************************************************
221 * timeGetSystemTime [WINMM.@]
223 MMRESULT WINAPI timeGetSystemTime(LPMMTIME lpTime, UINT wSize)
225 TRACE("(%p, %u);\n", lpTime, wSize);
227 if (wSize >= sizeof(*lpTime)) {
229 lpTime->wType = TIME_MS;
230 lpTime->u.ms = WINMM_SysTimeMS;
232 TRACE("=> %lu\n", lpTime->u.ms);
238 /**************************************************************************
239 * TIME_SetEventInternal [internal]
241 WORD TIME_SetEventInternal(UINT wDelay, UINT wResol,
242 LPTIMECALLBACK lpFunc, DWORD dwUser, UINT wFlags)
245 LPWINE_TIMERENTRY lpNewTimer;
246 LPWINE_TIMERENTRY lpTimer;
248 TRACE("(%u, %u, %p, %08lX, %04X);\n", wDelay, wResol, lpFunc, dwUser, wFlags);
250 lpNewTimer = (LPWINE_TIMERENTRY)HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_TIMERENTRY));
251 if (lpNewTimer == NULL)
254 if (wDelay < MMSYSTIME_MININTERVAL || wDelay > MMSYSTIME_MAXINTERVAL)
259 lpNewTimer->uCurTime = wDelay;
260 lpNewTimer->wDelay = wDelay;
261 lpNewTimer->wResol = wResol;
262 lpNewTimer->lpFunc = lpFunc;
263 lpNewTimer->dwUser = dwUser;
264 lpNewTimer->wFlags = wFlags;
266 EnterCriticalSection(&WINMM_IData->cs);
268 if ((wFlags & TIME_KILL_SYNCHRONOUS) && !TIME_hKillEvent)
269 TIME_hKillEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
271 for (lpTimer = TIME_TimersList; lpTimer != NULL; lpTimer = lpTimer->lpNext) {
272 wNewID = max(wNewID, lpTimer->wTimerID);
275 lpNewTimer->lpNext = TIME_TimersList;
276 TIME_TimersList = lpNewTimer;
277 lpNewTimer->wTimerID = wNewID + 1;
279 LeaveCriticalSection(&WINMM_IData->cs);
281 TRACE("=> %u\n", wNewID + 1);
286 /**************************************************************************
287 * timeSetEvent [WINMM.@]
289 MMRESULT WINAPI timeSetEvent(UINT wDelay, UINT wResol, LPTIMECALLBACK lpFunc,
290 DWORD_PTR dwUser, UINT wFlags)
292 if (wFlags & WINE_TIMER_IS32)
293 WARN("Unknown windows flag... wine internally used.. ooch\n");
295 return TIME_SetEventInternal(wDelay, wResol, lpFunc,
296 dwUser, wFlags|WINE_TIMER_IS32);
299 /**************************************************************************
300 * timeKillEvent [WINMM.@]
302 MMRESULT WINAPI timeKillEvent(UINT wID)
304 LPWINE_TIMERENTRY lpSelf = NULL, *lpTimer;
306 TRACE("(%u)\n", wID);
307 EnterCriticalSection(&WINMM_IData->cs);
308 /* remove WINE_TIMERENTRY from list */
309 for (lpTimer = &TIME_TimersList; *lpTimer; lpTimer = &(*lpTimer)->lpNext) {
310 if (wID == (*lpTimer)->wTimerID) {
312 /* unlink timer of id 'wID' */
313 *lpTimer = (*lpTimer)->lpNext;
317 LeaveCriticalSection(&WINMM_IData->cs);
321 WARN("wID=%u is not a valid timer ID\n", wID);
322 return MMSYSERR_INVALPARAM;
324 if (lpSelf->wFlags & TIME_KILL_SYNCHRONOUS)
325 WaitForSingleObject(TIME_hKillEvent, INFINITE);
326 HeapFree(GetProcessHeap(), 0, lpSelf);
327 return TIMERR_NOERROR;
330 /**************************************************************************
331 * timeGetDevCaps [WINMM.@]
333 MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS lpCaps, UINT wSize)
335 TRACE("(%p, %u) !\n", lpCaps, wSize);
337 lpCaps->wPeriodMin = MMSYSTIME_MININTERVAL;
338 lpCaps->wPeriodMax = MMSYSTIME_MAXINTERVAL;
342 /**************************************************************************
343 * timeBeginPeriod [WINMM.@]
345 MMRESULT WINAPI timeBeginPeriod(UINT wPeriod)
347 TRACE("(%u) !\n", wPeriod);
349 if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
350 return TIMERR_NOCANDO;
354 /**************************************************************************
355 * timeEndPeriod [WINMM.@]
357 MMRESULT WINAPI timeEndPeriod(UINT wPeriod)
359 TRACE("(%u) !\n", wPeriod);
361 if (wPeriod < MMSYSTIME_MININTERVAL || wPeriod > MMSYSTIME_MAXINTERVAL)
362 return TIMERR_NOCANDO;
366 /**************************************************************************
367 * timeGetTime [MMSYSTEM.607]
368 * timeGetTime [WINMM.@]
370 DWORD WINAPI timeGetTime(void)
373 /* FIXME: releasing the win16 lock here is a temporary hack (I hope)
374 * that lets mciavi.drv run correctly
376 if (pFnReleaseThunkLock) pFnReleaseThunkLock(&count);
378 if (pFnRestoreThunkLock) pFnRestoreThunkLock(count);
379 return WINMM_SysTimeMS;