1/*
2 * tclWinNotify.c --
3 *
4 *	This file contains Windows-specific procedures for the notifier,
5 *	which is the lowest-level part of the Tcl event loop.  This file
6 *	works together with ../generic/tclNotify.c.
7 *
8 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
9 *
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id: tclWinNotify.c,v 1.11.2.1 2003/03/21 03:24:09 dgp Exp $
14 */
15
16#include "tclWinInt.h"
17
18/*
19 * The follwing static indicates whether this module has been initialized.
20 */
21
22#define INTERVAL_TIMER 1	/* Handle of interval timer. */
23
24#define WM_WAKEUP WM_USER	/* Message that is send by
25				 * Tcl_AlertNotifier. */
26/*
27 * The following static structure contains the state information for the
28 * Windows implementation of the Tcl notifier.  One of these structures
29 * is created for each thread that is using the notifier.
30 */
31
32typedef struct ThreadSpecificData {
33    CRITICAL_SECTION crit;	/* Monitor for this notifier. */
34    DWORD thread;		/* Identifier for thread associated with this
35				 * notifier. */
36    HANDLE event;		/* Event object used to wake up the notifier
37				 * thread. */
38    int pending;		/* Alert message pending, this field is
39				 * locked by the notifierMutex. */
40    HWND hwnd;			/* Messaging window. */
41    int timeout;		/* Current timeout value. */
42    int timerActive;		/* 1 if interval timer is running. */
43} ThreadSpecificData;
44
45static Tcl_ThreadDataKey dataKey;
46
47extern TclStubs tclStubs;
48extern Tcl_NotifierProcs tclOriginalNotifier;
49
50/*
51 * The following static indicates the number of threads that have
52 * initialized notifiers.  It controls the lifetime of the TclNotifier
53 * window class.
54 *
55 * You must hold the notifierMutex lock before accessing this variable.
56 */
57
58static int notifierCount = 0;
59TCL_DECLARE_MUTEX(notifierMutex)
60
61/*
62 * Static routines defined in this file.
63 */
64
65static LRESULT CALLBACK	NotifierProc(HWND hwnd, UINT message,
66			    WPARAM wParam, LPARAM lParam);
67
68
69/*
70 *----------------------------------------------------------------------
71 *
72 * Tcl_InitNotifier --
73 *
74 *	Initializes the platform specific notifier state.
75 *
76 * Results:
77 *	Returns a handle to the notifier state for this thread..
78 *
79 * Side effects:
80 *	None.
81 *
82 *----------------------------------------------------------------------
83 */
84
85ClientData
86Tcl_InitNotifier()
87{
88    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
89    WNDCLASS class;
90
91    /*
92     * Register Notifier window class if this is the first thread to
93     * use this module.
94     */
95
96    Tcl_MutexLock(&notifierMutex);
97    if (notifierCount == 0) {
98	class.style = 0;
99	class.cbClsExtra = 0;
100	class.cbWndExtra = 0;
101	class.hInstance = TclWinGetTclInstance();
102	class.hbrBackground = NULL;
103	class.lpszMenuName = NULL;
104	class.lpszClassName = "TclNotifier";
105	class.lpfnWndProc = NotifierProc;
106	class.hIcon = NULL;
107	class.hCursor = NULL;
108
109	if (!RegisterClassA(&class)) {
110	    panic("Unable to register TclNotifier window class");
111	}
112    }
113    notifierCount++;
114    Tcl_MutexUnlock(&notifierMutex);
115
116    tsdPtr->pending = 0;
117    tsdPtr->timerActive = 0;
118
119    InitializeCriticalSection(&tsdPtr->crit);
120
121    tsdPtr->hwnd = NULL;
122    tsdPtr->thread = GetCurrentThreadId();
123    tsdPtr->event = CreateEvent(NULL, TRUE /* manual */,
124	    FALSE /* !signaled */, NULL);
125
126    return (ClientData) tsdPtr;
127}
128
129/*
130 *----------------------------------------------------------------------
131 *
132 * Tcl_FinalizeNotifier --
133 *
134 *	This function is called to cleanup the notifier state before
135 *	a thread is terminated.
136 *
137 * Results:
138 *	None.
139 *
140 * Side effects:
141 *	May dispose of the notifier window and class.
142 *
143 *----------------------------------------------------------------------
144 */
145
146void
147Tcl_FinalizeNotifier(clientData)
148    ClientData clientData;	/* Pointer to notifier data. */
149{
150    ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
151
152    /*
153     * Only finalize the notifier if a notifier was installed in the
154     * current thread; there is a route in which this is not
155     * guaranteed to be true (when tclWin32Dll.c:DllMain() is called
156     * with the flag DLL_PROCESS_DETACH by the OS, which could be
157     * doing so from a thread that's never previously been involved
158     * with Tcl, e.g. the task manager) so this check is important.
159     *
160     * Fixes Bug #217982 reported by Hugh Vu and Gene Leache.
161     */
162    if (tsdPtr == NULL) {
163	return;
164    }
165
166    DeleteCriticalSection(&tsdPtr->crit);
167    CloseHandle(tsdPtr->event);
168
169    /*
170     * Clean up the timer and messaging window for this thread.
171     */
172
173    if (tsdPtr->hwnd) {
174	KillTimer(tsdPtr->hwnd, INTERVAL_TIMER);
175	DestroyWindow(tsdPtr->hwnd);
176    }
177
178    /*
179     * If this is the last thread to use the notifier, unregister
180     * the notifier window class.
181     */
182
183    Tcl_MutexLock(&notifierMutex);
184    notifierCount--;
185    if (notifierCount == 0) {
186	UnregisterClassA("TclNotifier", TclWinGetTclInstance());
187    }
188    Tcl_MutexUnlock(&notifierMutex);
189}
190
191/*
192 *----------------------------------------------------------------------
193 *
194 * Tcl_AlertNotifier --
195 *
196 *	Wake up the specified notifier from any thread. This routine
197 *	is called by the platform independent notifier code whenever
198 *	the Tcl_ThreadAlert routine is called.  This routine is
199 *	guaranteed not to be called on a given notifier after
200 *	Tcl_FinalizeNotifier is called for that notifier.  This routine
201 *	is typically called from a thread other than the notifier's
202 *	thread.
203 *
204 * Results:
205 *	None.
206 *
207 * Side effects:
208 *	Sends a message to the messaging window for the notifier
209 *	if there isn't already one pending.
210 *
211 *----------------------------------------------------------------------
212 */
213
214void
215Tcl_AlertNotifier(clientData)
216    ClientData clientData;	/* Pointer to thread data. */
217{
218    ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData;
219
220    /*
221     * Note that we do not need to lock around access to the hwnd
222     * because the race condition has no effect since any race condition
223     * implies that the notifier thread is already awake.
224     */
225
226    if (tsdPtr->hwnd) {
227	/*
228	 * We do need to lock around access to the pending flag.
229	 */
230
231	EnterCriticalSection(&tsdPtr->crit);
232	if (!tsdPtr->pending) {
233	    PostMessage(tsdPtr->hwnd, WM_WAKEUP, 0, 0);
234	}
235	tsdPtr->pending = 1;
236	LeaveCriticalSection(&tsdPtr->crit);
237    } else {
238	SetEvent(tsdPtr->event);
239    }
240}
241
242/*
243 *----------------------------------------------------------------------
244 *
245 * Tcl_SetTimer --
246 *
247 *	This procedure sets the current notifier timer value.  The
248 *	notifier will ensure that Tcl_ServiceAll() is called after
249 *	the specified interval, even if no events have occurred.
250 *
251 * Results:
252 *	None.
253 *
254 * Side effects:
255 *	Replaces any previous timer.
256 *
257 *----------------------------------------------------------------------
258 */
259
260void
261Tcl_SetTimer(
262    Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
263{
264    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
265    UINT timeout;
266
267    /*
268     * Allow the notifier to be hooked.  This may not make sense
269     * on Windows, but mirrors the UNIX hook.
270     */
271
272    if (tclStubs.tcl_SetTimer != tclOriginalNotifier.setTimerProc) {
273	tclStubs.tcl_SetTimer(timePtr);
274	return;
275    }
276
277    /*
278     * We only need to set up an interval timer if we're being called
279     * from an external event loop.  If we don't have a window handle
280     * then we just return immediately and let Tcl_WaitForEvent handle
281     * timeouts.
282     */
283
284    if (!tsdPtr->hwnd) {
285	return;
286    }
287
288    if (!timePtr) {
289	timeout = 0;
290    } else {
291	/*
292	 * Make sure we pass a non-zero value into the timeout argument.
293	 * Windows seems to get confused by zero length timers.
294	 */
295
296	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
297	if (timeout == 0) {
298	    timeout = 1;
299	}
300    }
301    tsdPtr->timeout = timeout;
302    if (timeout != 0) {
303	tsdPtr->timerActive = 1;
304	SetTimer(tsdPtr->hwnd, INTERVAL_TIMER,
305		    (unsigned long) tsdPtr->timeout, NULL);
306    } else {
307	tsdPtr->timerActive = 0;
308	KillTimer(tsdPtr->hwnd, INTERVAL_TIMER);
309    }
310}
311
312/*
313 *----------------------------------------------------------------------
314 *
315 * Tcl_ServiceModeHook --
316 *
317 *	This function is invoked whenever the service mode changes.
318 *
319 * Results:
320 *	None.
321 *
322 * Side effects:
323 *	If this is the first time the notifier is set into
324 *	TCL_SERVICE_ALL, then the communication window is created.
325 *
326 *----------------------------------------------------------------------
327 */
328
329void
330Tcl_ServiceModeHook(mode)
331    int mode;			/* Either TCL_SERVICE_ALL, or
332				 * TCL_SERVICE_NONE. */
333{
334    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
335
336    /*
337     * If this is the first time that the notifier has been used from a
338     * modal loop, then create a communication window.  Note that after
339     * this point, the application needs to service events in a timely
340     * fashion or Windows will hang waiting for the window to respond
341     * to synchronous system messages.  At some point, we may want to
342     * consider destroying the window if we leave the modal loop, but
343     * for now we'll leave it around.
344     */
345
346    if (mode == TCL_SERVICE_ALL && !tsdPtr->hwnd) {
347	tsdPtr->hwnd = CreateWindowA("TclNotifier", "TclNotifier", WS_TILED,
348		0, 0, 0, 0, NULL, NULL, TclWinGetTclInstance(), NULL);
349	/*
350	 * Send an initial message to the window to ensure that we wake up the
351	 * notifier once we get into the modal loop.  This will force the
352	 * notifier to recompute the timeout value and schedule a timer
353	 * if one is needed.
354	 */
355
356	Tcl_AlertNotifier((ClientData)tsdPtr);
357    }
358}
359
360/*
361 *----------------------------------------------------------------------
362 *
363 * NotifierProc --
364 *
365 *	This procedure is invoked by Windows to process events on
366 *	the notifier window.  Messages will be sent to this window
367 *	in response to external timer events or calls to
368 *	TclpAlertTsdPtr->
369 *
370 * Results:
371 *	A standard windows result.
372 *
373 * Side effects:
374 *	Services any pending events.
375 *
376 *----------------------------------------------------------------------
377 */
378
379static LRESULT CALLBACK
380NotifierProc(
381    HWND hwnd,
382    UINT message,
383    WPARAM wParam,
384    LPARAM lParam)
385{
386    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
387
388    if (message == WM_WAKEUP) {
389	EnterCriticalSection(&tsdPtr->crit);
390	tsdPtr->pending = 0;
391	LeaveCriticalSection(&tsdPtr->crit);
392    } else if (message != WM_TIMER) {
393	return DefWindowProc(hwnd, message, wParam, lParam);
394    }
395
396    /*
397     * Process all of the runnable events.
398     */
399
400    Tcl_ServiceAll();
401    return 0;
402}
403
404/*
405 *----------------------------------------------------------------------
406 *
407 * Tcl_WaitForEvent --
408 *
409 *	This function is called by Tcl_DoOneEvent to wait for new
410 *	events on the message queue.  If the block time is 0, then
411 *	Tcl_WaitForEvent just polls the event queue without blocking.
412 *
413 * Results:
414 *	Returns -1 if a WM_QUIT message is detected, returns 1 if
415 *	a message was dispatched, otherwise returns 0.
416 *
417 * Side effects:
418 *	Dispatches a message to a window procedure, which could do
419 *	anything.
420 *
421 *----------------------------------------------------------------------
422 */
423
424int
425Tcl_WaitForEvent(
426    Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
427{
428    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
429    MSG msg;
430    DWORD timeout, result;
431    int status;
432
433    /*
434     * Allow the notifier to be hooked.  This may not make
435     * sense on windows, but mirrors the UNIX hook.
436     */
437
438    if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) {
439	return tclStubs.tcl_WaitForEvent(timePtr);
440    }
441
442    /*
443     * Compute the timeout in milliseconds.
444     */
445
446    if (timePtr) {
447	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
448    } else {
449	timeout = INFINITE;
450    }
451
452    /*
453     * Check to see if there are any messages in the queue before waiting
454     * because MsgWaitForMultipleObjects will not wake up if there are events
455     * currently sitting in the queue.
456     */
457
458    if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
459	/*
460	 * Wait for something to happen (a signal from another thread, a
461	 * message, or timeout).
462	 */
463
464	result = MsgWaitForMultipleObjects(1, &tsdPtr->event, FALSE, timeout,
465		QS_ALLINPUT);
466    }
467
468    /*
469     * Check to see if there are any messages to process.
470     */
471
472    if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
473	/*
474	 * Retrieve and dispatch the first message.
475	 */
476
477	result = GetMessage(&msg, NULL, 0, 0);
478	if (result == 0) {
479	    /*
480	     * We received a request to exit this thread (WM_QUIT), so
481	     * propagate the quit message and start unwinding.
482	     */
483
484	    PostQuitMessage((int) msg.wParam);
485	    status = -1;
486	} else if (result == -1) {
487	    /*
488	     * We got an error from the system.  I have no idea why this would
489	     * happen, so we'll just unwind.
490	     */
491
492	    status = -1;
493	} else {
494	    TranslateMessage(&msg);
495	    DispatchMessage(&msg);
496	    status = 1;
497	}
498    } else {
499	status = 0;
500    }
501
502    ResetEvent(tsdPtr->event);
503    return status;
504}
505
506/*
507 *----------------------------------------------------------------------
508 *
509 * Tcl_Sleep --
510 *
511 *	Delay execution for the specified number of milliseconds.
512 *
513 * Results:
514 *	None.
515 *
516 * Side effects:
517 *	Time passes.
518 *
519 *----------------------------------------------------------------------
520 */
521
522void
523Tcl_Sleep(ms)
524    int ms;			/* Number of milliseconds to sleep. */
525{
526    /*
527     * Simply calling 'Sleep' for the requisite number of milliseconds
528     * can make the process appear to wake up early because it isn't
529     * synchronized with the CPU performance counter that is used in
530     * tclWinTime.c.  This behavior is probably benign, but messes
531     * up some of the corner cases in the test suite.  We get around
532     * this problem by repeating the 'Sleep' call as many times
533     * as necessary to make the clock advance by the requisite amount.
534     */
535
536    Tcl_Time now;		/* Current wall clock time */
537    Tcl_Time desired;		/* Desired wakeup time */
538    DWORD sleepTime = ms;	/* Time to sleep */
539
540    Tcl_GetTime( &now );
541    desired.sec = now.sec + ( ms / 1000 );
542    desired.usec = now.usec + 1000 * ( ms % 1000 );
543    if ( desired.usec > 1000000 ) {
544	++desired.sec;
545	desired.usec -= 1000000;
546    }
547
548    for ( ; ; ) {
549	Sleep( sleepTime );
550	Tcl_GetTime( &now );
551	if ( now.sec > desired.sec ) {
552	    break;
553	} else if ( ( now.sec == desired.sec )
554	     && ( now.usec >= desired.usec ) ) {
555	    break;
556	}
557	sleepTime = ( ( 1000 * ( desired.sec - now.sec ) )
558		      + ( ( desired.usec - now.usec ) / 1000 ) );
559    }
560
561}
562