| /* -*- 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  */ | 
 |  | 
 | #include "config.h" | 
 | #include "wine/port.h" | 
 |  | 
 | #include <stdarg.h> | 
 | #include <errno.h> | 
 | #include <time.h> | 
 | #ifdef HAVE_SYS_TIME_H | 
 | # include <sys/time.h> | 
 | #endif | 
 | #ifdef HAVE_UNISTD_H | 
 | # include <unistd.h> | 
 | #endif | 
 | #ifdef HAVE_POLL_H | 
 | #include <poll.h> | 
 | #endif | 
 | #ifdef HAVE_SYS_POLL_H | 
 | #include <sys/poll.h> | 
 | #endif | 
 |  | 
 | #include "windef.h" | 
 | #include "winbase.h" | 
 | #include "mmsystem.h" | 
 |  | 
 | #include "winemm.h" | 
 |  | 
 | #include "wine/list.h" | 
 | #include "wine/debug.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(mmtime); | 
 |  | 
 | typedef struct tagWINE_TIMERENTRY { | 
 |     struct list                 entry; | 
 |     UINT                        wDelay; | 
 |     UINT                        wResol; | 
 |     LPTIMECALLBACK              lpFunc; /* can be lots of things */ | 
 |     DWORD                       dwUser; | 
 |     UINT16                      wFlags; | 
 |     UINT16                      wTimerID; | 
 |     DWORD                       dwTriggerTime; | 
 | } WINE_TIMERENTRY, *LPWINE_TIMERENTRY; | 
 |  | 
 | static struct list timer_list = LIST_INIT(timer_list); | 
 |  | 
 | static CRITICAL_SECTION TIME_cbcrst; | 
 | static CRITICAL_SECTION_DEBUG critsect_debug = | 
 | { | 
 |     0, 0, &TIME_cbcrst, | 
 |     { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, | 
 |       0, 0, { (DWORD_PTR)(__FILE__ ": TIME_cbcrst") } | 
 | }; | 
 | static CRITICAL_SECTION TIME_cbcrst = { &critsect_debug, -1, 0, 0, 0, 0 }; | 
 |  | 
 | static    HANDLE                TIME_hMMTimer; | 
 | static    BOOL                  TIME_TimeToDie = TRUE; | 
 | static    int                   TIME_fdWake[2] = { -1, -1 }; | 
 |  | 
 | /* link timer at the appropriate spot in the list */ | 
 | static inline void link_timer( WINE_TIMERENTRY *timer ) | 
 | { | 
 |     WINE_TIMERENTRY *next; | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( next, &timer_list, WINE_TIMERENTRY, entry ) | 
 |         if ((int)(next->dwTriggerTime - timer->dwTriggerTime) >= 0) break; | 
 |  | 
 |     list_add_before( &next->entry, &timer->entry ); | 
 | } | 
 |  | 
 | /* | 
 |  * 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) | 
 |  | 
 |  | 
 | /************************************************************************** | 
 |  *           TIME_MMSysTimeCallback | 
 |  */ | 
 | static int TIME_MMSysTimeCallback(void) | 
 | { | 
 |     WINE_TIMERENTRY *timer, *to_free; | 
 |     int delta_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. | 
 |      */ | 
 |  | 
 |     EnterCriticalSection(&WINMM_cs); | 
 |     for (;;) | 
 |     { | 
 |         struct list *ptr = list_head( &timer_list ); | 
 |         if (!ptr) | 
 |         { | 
 |             delta_time = -1; | 
 |             break; | 
 |         } | 
 |  | 
 |         timer = LIST_ENTRY( ptr, WINE_TIMERENTRY, entry ); | 
 |         delta_time = timer->dwTriggerTime - GetTickCount(); | 
 |         if (delta_time > 0) break; | 
 |  | 
 |         list_remove( &timer->entry ); | 
 |         if (timer->wFlags & TIME_PERIODIC) | 
 |         { | 
 |             timer->dwTriggerTime += timer->wDelay; | 
 |             link_timer( timer );  /* restart it */ | 
 |             to_free = NULL; | 
 |         } | 
 |         else to_free = timer; | 
 |  | 
 |         switch(timer->wFlags & (TIME_CALLBACK_EVENT_SET|TIME_CALLBACK_EVENT_PULSE)) | 
 |         { | 
 |         case TIME_CALLBACK_EVENT_SET: | 
 |             SetEvent((HANDLE)timer->lpFunc); | 
 |             break; | 
 |         case TIME_CALLBACK_EVENT_PULSE: | 
 |             PulseEvent((HANDLE)timer->lpFunc); | 
 |             break; | 
 |         case TIME_CALLBACK_FUNCTION: | 
 |             { | 
 |                 DWORD user = timer->dwUser; | 
 |                 UINT16 id = timer->wTimerID; | 
 |                 UINT16 flags = timer->wFlags; | 
 |                 LPTIMECALLBACK func = timer->lpFunc; | 
 |  | 
 |                 if (flags & TIME_KILL_SYNCHRONOUS) EnterCriticalSection(&TIME_cbcrst); | 
 |                 LeaveCriticalSection(&WINMM_cs); | 
 |  | 
 |                 if (flags & WINE_TIMER_IS32) func(id, 0, user, 0, 0); | 
 |                 else if (pFnCallMMDrvFunc16) pFnCallMMDrvFunc16((DWORD)func, id, 0, user, 0, 0); | 
 |  | 
 |                 EnterCriticalSection(&WINMM_cs); | 
 |                 if (flags & TIME_KILL_SYNCHRONOUS) LeaveCriticalSection(&TIME_cbcrst); | 
 |             } | 
 |             break; | 
 |         } | 
 |         HeapFree( GetProcessHeap(), 0, to_free ); | 
 |     } | 
 |     LeaveCriticalSection(&WINMM_cs); | 
 |     return delta_time; | 
 | } | 
 |  | 
 | /************************************************************************** | 
 |  *           TIME_MMSysTimeThread | 
 |  */ | 
 | static DWORD CALLBACK TIME_MMSysTimeThread(LPVOID arg) | 
 | { | 
 |     int sleep_time, ret; | 
 |     char readme[16]; | 
 |     struct pollfd pfd; | 
 |  | 
 |     pfd.fd = TIME_fdWake[0]; | 
 |     pfd.events = POLLIN; | 
 |  | 
 |     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(); | 
 |  | 
 |         if (sleep_time == 0) | 
 |             continue; | 
 |  | 
 |         if ((ret = poll(&pfd, 1, sleep_time)) < 0) | 
 |         { | 
 |             if (errno != EINTR && errno != EAGAIN) | 
 |             { | 
 |                 ERR("Unexpected error in poll: %s(%d)\n", strerror(errno), errno); | 
 |                 break; | 
 |             } | 
 |          } | 
 |  | 
 |         while (ret > 0) ret = read(TIME_fdWake[0], readme, sizeof(readme)); | 
 |     } | 
 |     TRACE("Exiting main winmm thread\n"); | 
 |     return 0; | 
 | } | 
 |  | 
 | /************************************************************************** | 
 |  * 				TIME_MMTimeStart | 
 |  */ | 
 | static void TIME_MMTimeStart(void) | 
 | { | 
 |     if (!TIME_hMMTimer) { | 
 |         if (pipe(TIME_fdWake) < 0) | 
 |         { | 
 |             TIME_fdWake[0] = TIME_fdWake[1] = -1; | 
 |             ERR("Cannot create pipe: %s\n", strerror(errno)); | 
 |         } else { | 
 |             fcntl(TIME_fdWake[0], F_SETFL, O_NONBLOCK); | 
 |             fcntl(TIME_fdWake[1], F_SETFL, O_NONBLOCK); | 
 |         } | 
 |         TIME_TimeToDie = FALSE; | 
 |         TIME_hMMTimer = CreateThread(NULL, 0, TIME_MMSysTimeThread, NULL, 0, NULL); | 
 |         SetThreadPriority(TIME_hMMTimer, THREAD_PRIORITY_TIME_CRITICAL); | 
 |     } | 
 | } | 
 |  | 
 | /************************************************************************** | 
 |  * 				TIME_MMTimeStop | 
 |  */ | 
 | void	TIME_MMTimeStop(void) | 
 | { | 
 |     if (TIME_hMMTimer) { | 
 |         const char a='a'; | 
 |  | 
 |         TIME_TimeToDie = TRUE; | 
 |         write(TIME_fdWake[1], &a, sizeof(a)); | 
 |  | 
 |         WaitForSingleObject(TIME_hMMTimer, INFINITE); | 
 |         close(TIME_fdWake[0]); | 
 |         close(TIME_fdWake[1]); | 
 |         TIME_fdWake[0] = TIME_fdWake[1] = -1; | 
 |         CloseHandle(TIME_hMMTimer); | 
 |         TIME_hMMTimer = 0; | 
 |         DeleteCriticalSection(&TIME_cbcrst); | 
 |     } | 
 | } | 
 |  | 
 | /************************************************************************** | 
 |  * 				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; | 
 |     const char c = 'c'; | 
 |  | 
 |     TRACE("(%u, %u, %p, %08X, %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; | 
 |  | 
 |     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_cs); | 
 |  | 
 |     LIST_FOR_EACH_ENTRY( lpTimer, &timer_list, WINE_TIMERENTRY, entry ) | 
 |         wNewID = max(wNewID, lpTimer->wTimerID); | 
 |  | 
 |     link_timer( lpNewTimer ); | 
 |     lpNewTimer->wTimerID = wNewID + 1; | 
 |  | 
 |     TIME_MMTimeStart(); | 
 |  | 
 |     LeaveCriticalSection(&WINMM_cs); | 
 |  | 
 |     /* Wake the service thread in case there is work to be done */ | 
 |     write(TIME_fdWake[1], &c, sizeof(c)); | 
 |  | 
 |     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) | 
 | { | 
 |     WINE_TIMERENTRY *lpSelf = NULL, *lpTimer; | 
 |     DWORD wFlags; | 
 |  | 
 |     TRACE("(%u)\n", wID); | 
 |     EnterCriticalSection(&WINMM_cs); | 
 |     /* remove WINE_TIMERENTRY from list */ | 
 |     LIST_FOR_EACH_ENTRY( lpTimer, &timer_list, WINE_TIMERENTRY, entry ) | 
 |     { | 
 | 	if (wID == lpTimer->wTimerID) { | 
 |             lpSelf = lpTimer; | 
 |             list_remove( &lpTimer->entry ); | 
 | 	    break; | 
 | 	} | 
 |     } | 
 |     LeaveCriticalSection(&WINMM_cs); | 
 |  | 
 |     if (!lpSelf) | 
 |     { | 
 |         WARN("wID=%u is not a valid timer ID\n", wID); | 
 |         return MMSYSERR_INVALPARAM; | 
 |     } | 
 |     wFlags = lpSelf->wFlags; | 
 |     if (wFlags & TIME_KILL_SYNCHRONOUS) | 
 |         EnterCriticalSection(&TIME_cbcrst); | 
 |     HeapFree(GetProcessHeap(), 0, lpSelf); | 
 |     if (wFlags & TIME_KILL_SYNCHRONOUS) | 
 |         LeaveCriticalSection(&TIME_cbcrst); | 
 |     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 mciavi32.dll run correctly | 
 |      */ | 
 |     if (pFnReleaseThunkLock) pFnReleaseThunkLock(&count); | 
 |     if (pFnRestoreThunkLock) pFnRestoreThunkLock(count); | 
 | #endif | 
 |  | 
 |     return GetTickCount(); | 
 | } |