diff --git a/reactos/subsys/win32k/ntuser/stubs.c b/reactos/subsys/win32k/ntuser/stubs.c index 6b2c536a733..fbb0052200e 100644 --- a/reactos/subsys/win32k/ntuser/stubs.c +++ b/reactos/subsys/win32k/ntuser/stubs.c @@ -1120,17 +1120,6 @@ NtUserIsClipboardFormatAvailable( return 0; } -DWORD -STDCALL -NtUserKillTimer( - DWORD Unknown0, - DWORD Unknown1) -{ - UNIMPLEMENTED - - return 0; -} - DWORD STDCALL NtUserLoadKeyboardLayoutEx( @@ -1559,19 +1548,6 @@ NtUserSetSystemMenu( return 0; } -DWORD -STDCALL -NtUserSetSystemTimer( - DWORD Unknown0, - DWORD Unknown1, - DWORD Unknown2, - DWORD Unknown3) -{ - UNIMPLEMENTED - - return 0; -} - DWORD STDCALL NtUserSetThreadState( @@ -1583,19 +1559,6 @@ NtUserSetThreadState( return 0; } -DWORD -STDCALL -NtUserSetTimer( - DWORD Unknown0, - DWORD Unknown1, - DWORD Unknown2, - DWORD Unknown3) -{ - UNIMPLEMENTED - - return 0; -} - DWORD STDCALL NtUserShowCaret( diff --git a/reactos/subsys/win32k/ntuser/timer.c b/reactos/subsys/win32k/ntuser/timer.c new file mode 100644 index 00000000000..6d174a9c3d0 --- /dev/null +++ b/reactos/subsys/win32k/ntuser/timer.c @@ -0,0 +1,362 @@ +/* + * + * COPYRIGHT: See COPYING in the top level directory + * PROJECT: ReactOS kernel + * PURPOSE: Window timers messages + * FILE: subsys/win32k/ntuser/timer.c + * PROGRAMER: Gunnar + * REVISION HISTORY: + * + */ + +/* INCLUDES ******************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NDEBUG +#include + +/* GLOBALS *******************************************************************/ + + +static FAST_MUTEX Mutex; +static LIST_ENTRY TimerListHead; +static KTIMER Timer; +static RTL_BITMAP HandleLessTimersBitMap; +static PVOID HandleLessTimersBitMapBuffer; +static ULONG HintIndex = 0; +static HANDLE MsgTimerThreadHandle; +static CLIENT_ID MsgTimerThreadId; + + +typedef struct _MSG_TIMER_ENTRY{ + LIST_ENTRY ListEntry; + LARGE_INTEGER Timeout; + HANDLE ThreadID; + UINT Period; + MSG Msg; +} MSG_TIMER_ENTRY, *PMSG_TIMER_ENTRY; + + +/* FUNCTIONS *****************************************************************/ + + +//return true if the new timer became the first entry +BOOL +FASTCALL +InsertTimerAscendingOrder(PMSG_TIMER_ENTRY NewTimer) +{ + PLIST_ENTRY EnumEntry, InsertAfter; + PMSG_TIMER_ENTRY MsgTimer; + + InsertAfter = NULL; + + EnumEntry = TimerListHead.Flink; + while (EnumEntry != &TimerListHead) + { + MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry); + if (NewTimer->Timeout.QuadPart > MsgTimer->Timeout.QuadPart) + { + InsertAfter = EnumEntry; + } + EnumEntry = EnumEntry->Flink; + } + + if (InsertAfter) + { + InsertTailList(InsertAfter, &NewTimer->ListEntry); + return FALSE; + } + + //insert as first entry + InsertHeadList(&TimerListHead, &NewTimer->ListEntry); + return TRUE; + +} + +PMSG_TIMER_ENTRY +FASTCALL +RemoveTimer(HWND hWnd, UINT_PTR IDEvent, HANDLE ThreadID) +{ + PMSG_TIMER_ENTRY MsgTimer; + PLIST_ENTRY EnumEntry; + + //remove timer if allready in the queue + EnumEntry = TimerListHead.Flink; + while (EnumEntry != &TimerListHead) + { + MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry); + if (MsgTimer->Msg.hwnd == hWnd && + MsgTimer->Msg.wParam == (WPARAM)IDEvent && + MsgTimer->ThreadID == ThreadID) + { + RemoveEntryList(EnumEntry); + return MsgTimer; + } + EnumEntry = EnumEntry->Flink; + } + + return NULL; +} + + + + +NTSTATUS +STDCALL +NtUserSetTimer( + HWND hWnd, + UINT_PTR * IDEvent, + UINT Period, + TIMERPROC TimerFunc + ) +{ + ULONG Index; + PMSG_TIMER_ENTRY MsgTimer = NULL; + PMSG_TIMER_ENTRY NewTimer; + LARGE_INTEGER CurrentTime; + HANDLE ThreadID; + + //FIXME: WINE: window must be owned by the calling thread +#if 0 + if (hWnd && !(hWnd = WIN_IsCurrentThread(hWnd)) + { + return STATUS_UNSUCCESSFUL; + } + +#endif + + ThreadID = PsGetCurrentThreadId(); + KeQuerySystemTime(&CurrentTime); + ExAcquireFastMutex(&Mutex); + + if (hWnd == NULL) + { + //find a free, handle-less timer id + Index = RtlFindClearBitsAndSet(&HandleLessTimersBitMap, 1, HintIndex); + if (Index == -1) + { + return STATUS_UNSUCCESSFUL; + } + + *IDEvent = HintIndex = Index + 1; + } + else + { + //remove timer if allready in the queue + MsgTimer = RemoveTimer(hWnd, *IDEvent, ThreadID); + } + + if (MsgTimer) + { + //modify existing (removed) timer + NewTimer = MsgTimer; + + NewTimer->Period = Period; + NewTimer->Timeout.QuadPart = CurrentTime.QuadPart + (Period * 10000); + NewTimer->Msg.lParam = (LPARAM)TimerFunc; + } + else + { + //FIXME: use lookaside? + NewTimer = ExAllocatePool(PagedPool, sizeof(MSG_TIMER_ENTRY)); + + NewTimer->Msg.hwnd = hWnd; + NewTimer->Msg.message = WM_TIMER; + NewTimer->Msg.wParam = (WPARAM)*IDEvent; + NewTimer->Msg.lParam = (LPARAM)TimerFunc; + NewTimer->Period = Period; + NewTimer->Timeout.QuadPart = CurrentTime.QuadPart + (Period * 10000); + NewTimer->ThreadID = ThreadID; + } + + if (InsertTimerAscendingOrder(NewTimer)) + { + //new timer is first in queue and expires first + KeSetTimer(&Timer, NewTimer->Timeout, NULL); + } + + ExReleaseFastMutex(&Mutex); + + return STATUS_SUCCESS; +} + + +NTSTATUS +STDCALL +NtUserKillTimer( + HWND hWnd, + UINT_PTR IDEvent) +{ + PMSG_TIMER_ENTRY MsgTimer; + + ExAcquireFastMutex(&Mutex); + + //handle-less timer? + if (hWnd == NULL) + { + if (!RtlAreBitsSet(&HandleLessTimersBitMap, IDEvent - 1, 1)) + { + //bit was not set + ExReleaseFastMutex(&Mutex); + return STATUS_UNSUCCESSFUL; + } + + RtlClearBits(&HandleLessTimersBitMap, IDEvent - 1, 1); + } + + MsgTimer = RemoveTimer(hWnd, IDEvent, PsGetCurrentThreadId()); + + ExReleaseFastMutex(&Mutex); + + if (MsgTimer == NULL) + { + //didn't find timer + return STATUS_UNSUCCESSFUL; + } + + //FIXME: use lookaside? + ExFreePool(MsgTimer); + + return STATUS_SUCCESS; +} + + + + +DWORD +STDCALL +NtUserSetSystemTimer( + DWORD Unknown0, + DWORD Unknown1, + DWORD Unknown2, + DWORD Unknown3) +{ + UNIMPLEMENTED + + return 0; +} + + +static NTSTATUS STDCALL +TimerThreadMain() +{ + NTSTATUS Status; + LARGE_INTEGER CurrentTime; + PLIST_ENTRY EnumEntry; + PMSG_TIMER_ENTRY MsgTimer; + PETHREAD Thread; + + for (;;) + { + + Status = KeWaitForSingleObject( &Timer, + Executive, + KernelMode, + FALSE, + NULL + ); + if (!NT_SUCCESS(Status)) + { + DPRINT1("Error waiting in TimerThreadMain\n"); + KeBugCheck(0); + } + + ExAcquireFastMutex(&Mutex); + + KeQuerySystemTime(&CurrentTime); + + EnumEntry = TimerListHead.Flink; + while (EnumEntry != &TimerListHead) + { + MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry); + EnumEntry = EnumEntry->Flink; + + if (CurrentTime.QuadPart >= MsgTimer->Timeout.QuadPart) + { + RemoveEntryList(&MsgTimer->ListEntry); + + /* + * FIXME: 1) Find a faster way of getting the thread message queue? (lookup by id is slow) + * 2) Kill all timers for thread when the thread exits? + */ + + if (!NT_SUCCESS(PsLookupThreadByThreadId(MsgTimer->ThreadID, &Thread))) + { + //FIXME: remove all other timers for this thread also? + ExFreePool(MsgTimer); + continue; + } + + /* + * FIXME: small window here, where the thread can exit between the thread lookup + * and the message posting (missing ref count?) + */ + MsqPostMessage(((PW32THREAD)Thread->Win32Thread)->MessageQueue, MsqCreateMessage(&MsgTimer->Msg)); + + //set up next periodic timeout + MsgTimer->Timeout.QuadPart += (MsgTimer->Period * 10000); + InsertTimerAscendingOrder(MsgTimer); + + } + else + { + break; + } + } + + //set up next timeout from first entry (if any) + if (!IsListEmpty(&TimerListHead)) + { + MsgTimer = CONTAINING_RECORD( TimerListHead.Flink, MSG_TIMER_ENTRY, ListEntry); + KeSetTimer(&Timer, MsgTimer->Timeout, NULL); + } + + ExReleaseFastMutex(&Mutex); + + } + +} + + + +NTSTATUS +InitTimerImpl() +{ + NTSTATUS Status; + + InitializeListHead(&TimerListHead); + KeInitializeTimer(&Timer); + ExInitializeFastMutex(&Mutex); + + //windows 2000 has room for 32768 handle-less timers + HandleLessTimersBitMapBuffer = ExAllocatePool(PagedPool, PAGE_SIZE); + RtlInitializeBitMap(&HandleLessTimersBitMap, + HandleLessTimersBitMapBuffer, + PAGE_SIZE * sizeof(ULONG)); + + //yes we need this, since ExAllocatePool isn't supposed to zero out allocated memory + RtlClearAllBits(&HandleLessTimersBitMap); + + Status = PsCreateSystemThread(&MsgTimerThreadHandle, + THREAD_ALL_ACCESS, + NULL, + NULL, + &MsgTimerThreadId, + TimerThreadMain, + NULL); + return Status; +} + + + + + +