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(¬ifierMutex); 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(¬ifierMutex); 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(¬ifierMutex); 184 notifierCount--; 185 if (notifierCount == 0) { 186 UnregisterClassA("TclNotifier", TclWinGetTclInstance()); 187 } 188 Tcl_MutexUnlock(¬ifierMutex); 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