OSDN Git Service

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