Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/win/tclWinNotify.c @ 25

Last change on this file since 25 was 25, checked in by landauf, 16 years ago

added tcl to libs

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