Jack Jansen | b0195ec | 1998-08-18 14:51:27 +0000 | [diff] [blame] | 1 | /* |
| 2 | * tclMacNotify.c -- |
| 3 | * |
| 4 | * This file contains Macintosh-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-1996 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 | * SCCS: @(#) tclMacNotify.c 1.36 97/05/07 19:09:29 |
| 14 | */ |
| 15 | |
| 16 | #include "tclInt.h" |
| 17 | #include "tclPort.h" |
| 18 | #include "tclMac.h" |
| 19 | #include "tclMacInt.h" |
| 20 | #include <signal.h> |
| 21 | #include <Events.h> |
| 22 | #include <LowMem.h> |
| 23 | #include <Processes.h> |
| 24 | #include <Timer.h> |
| 25 | |
| 26 | |
| 27 | /* |
| 28 | * This is necessary to work around a bug in Apple's Universal header files |
| 29 | * for the CFM68K libraries. |
| 30 | */ |
| 31 | |
| 32 | #ifdef __CFM68K__ |
| 33 | #undef GetEventQueue |
| 34 | extern pascal QHdrPtr GetEventQueue(void) |
| 35 | THREEWORDINLINE(0x2EBC, 0x0000, 0x014A); |
| 36 | #pragma import list GetEventQueue |
| 37 | #define GetEvQHdr() GetEventQueue() |
| 38 | #endif |
| 39 | |
| 40 | /* |
| 41 | * The follwing static indicates whether this module has been initialized. |
| 42 | */ |
| 43 | |
| 44 | static int initialized = 0; |
| 45 | |
| 46 | /* |
| 47 | * The following structure contains the state information for the |
| 48 | * notifier module. |
| 49 | */ |
| 50 | |
| 51 | static struct { |
| 52 | int timerActive; /* 1 if timer is running. */ |
| 53 | Tcl_Time timer; /* Time when next timer event is expected. */ |
| 54 | int flags; /* OR'ed set of flags defined below. */ |
| 55 | Point lastMousePosition; /* Last known mouse location. */ |
| 56 | RgnHandle utilityRgn; /* Region used as the mouse region for |
| 57 | * WaitNextEvent and the update region when |
| 58 | * checking for events. */ |
| 59 | Tcl_MacConvertEventPtr eventProcPtr; |
| 60 | /* This pointer holds the address of the |
| 61 | * function that will handle all incoming |
| 62 | * Macintosh events. */ |
| 63 | } notifier; |
| 64 | |
| 65 | /* |
| 66 | * The following defines are used in the flags field of the notifier struct. |
| 67 | */ |
| 68 | |
| 69 | #define NOTIFY_IDLE (1<<1) /* Tcl_ServiceIdle should be called. */ |
| 70 | #define NOTIFY_TIMER (1<<2) /* Tcl_ServiceTimer should be called. */ |
| 71 | |
| 72 | /* |
| 73 | * Prototypes for procedures that are referenced only in this file: |
| 74 | */ |
| 75 | |
| 76 | static int HandleMacEvents _ANSI_ARGS_((void)); |
| 77 | static void InitNotifier _ANSI_ARGS_((void)); |
| 78 | static void NotifierExitHandler _ANSI_ARGS_(( |
| 79 | ClientData clientData)); |
| 80 | |
| 81 | /* |
| 82 | *---------------------------------------------------------------------- |
| 83 | * |
| 84 | * InitNotifier -- |
| 85 | * |
| 86 | * Initializes the notifier structure. |
| 87 | * |
| 88 | * Results: |
| 89 | * None. |
| 90 | * |
| 91 | * Side effects: |
| 92 | * Creates a new exit handler. |
| 93 | * |
| 94 | *---------------------------------------------------------------------- |
| 95 | */ |
| 96 | |
| 97 | static void |
| 98 | InitNotifier(void) |
| 99 | { |
| 100 | initialized = 1; |
| 101 | memset(¬ifier, 0, sizeof(notifier)); |
| 102 | Tcl_CreateExitHandler(NotifierExitHandler, NULL); |
| 103 | } |
| 104 | |
| 105 | /* |
| 106 | *---------------------------------------------------------------------- |
| 107 | * |
| 108 | * NotifierExitHandler -- |
| 109 | * |
| 110 | * This function is called to cleanup the notifier state before |
| 111 | * Tcl is unloaded. |
| 112 | * |
| 113 | * Results: |
| 114 | * None. |
| 115 | * |
| 116 | * Side effects: |
| 117 | * None. |
| 118 | * |
| 119 | *---------------------------------------------------------------------- |
| 120 | */ |
| 121 | |
| 122 | static void |
| 123 | NotifierExitHandler( |
| 124 | ClientData clientData) /* Not used. */ |
| 125 | { |
| 126 | initialized = 0; |
| 127 | } |
| 128 | |
| 129 | /* |
| 130 | *---------------------------------------------------------------------- |
| 131 | * |
| 132 | * HandleMacEvents -- |
| 133 | * |
| 134 | * This function checks for events from the Macintosh event queue. |
| 135 | * |
| 136 | * Results: |
| 137 | * Returns 1 if event found, 0 otherwise. |
| 138 | * |
| 139 | * Side effects: |
| 140 | * Pulls events off of the Mac event queue and then calls |
| 141 | * convertEventProc. |
| 142 | * |
| 143 | *---------------------------------------------------------------------- |
| 144 | */ |
| 145 | |
| 146 | static int |
| 147 | HandleMacEvents(void) |
| 148 | { |
| 149 | EventRecord theEvent; |
| 150 | int eventFound = 0, needsUpdate = 0; |
| 151 | Point currentMouse; |
| 152 | WindowRef windowRef; |
| 153 | Rect mouseRect; |
| 154 | |
| 155 | /* |
| 156 | * Check for mouse moved events. These events aren't placed on the |
| 157 | * system event queue unless we call WaitNextEvent. |
| 158 | */ |
| 159 | |
| 160 | GetGlobalMouse(¤tMouse); |
| 161 | if ((notifier.eventProcPtr != NULL) && |
| 162 | !EqualPt(currentMouse, notifier.lastMousePosition)) { |
| 163 | notifier.lastMousePosition = currentMouse; |
| 164 | theEvent.what = nullEvent; |
| 165 | if ((*notifier.eventProcPtr)(&theEvent) == true) { |
| 166 | eventFound = 1; |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | * Check for update events. Since update events aren't generated |
| 172 | * until we call GetNextEvent, we may need to force a call to |
| 173 | * GetNextEvent, even if the queue is empty. |
| 174 | */ |
| 175 | |
| 176 | for (windowRef = FrontWindow(); windowRef != NULL; |
| 177 | windowRef = GetNextWindow(windowRef)) { |
| 178 | GetWindowUpdateRgn(windowRef, notifier.utilityRgn); |
| 179 | if (!EmptyRgn(notifier.utilityRgn)) { |
| 180 | needsUpdate = 1; |
| 181 | break; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | /* |
| 186 | * Process events from the OS event queue. |
| 187 | */ |
| 188 | |
| 189 | while (needsUpdate || (GetEvQHdr()->qHead != NULL)) { |
| 190 | GetGlobalMouse(¤tMouse); |
| 191 | SetRect(&mouseRect, currentMouse.h, currentMouse.v, |
| 192 | currentMouse.h + 1, currentMouse.v + 1); |
| 193 | RectRgn(notifier.utilityRgn, &mouseRect); |
| 194 | |
| 195 | WaitNextEvent(everyEvent, &theEvent, 5, notifier.utilityRgn); |
| 196 | needsUpdate = 0; |
| 197 | if ((notifier.eventProcPtr != NULL) |
| 198 | && ((*notifier.eventProcPtr)(&theEvent) == true)) { |
| 199 | eventFound = 1; |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | return eventFound; |
| 204 | } |
| 205 | |
| 206 | /* |
| 207 | *---------------------------------------------------------------------- |
| 208 | * |
| 209 | * Tcl_SetTimer -- |
| 210 | * |
| 211 | * This procedure sets the current notifier timer value. The |
| 212 | * notifier will ensure that Tcl_ServiceAll() is called after |
| 213 | * the specified interval, even if no events have occurred. |
| 214 | * |
| 215 | * Results: |
| 216 | * None. |
| 217 | * |
| 218 | * Side effects: |
| 219 | * Replaces any previous timer. |
| 220 | * |
| 221 | *---------------------------------------------------------------------- |
| 222 | */ |
| 223 | |
| 224 | void |
| 225 | Tcl_SetTimer( |
| 226 | Tcl_Time *timePtr) /* New value for interval timer. */ |
| 227 | { |
| 228 | if (!timePtr) { |
| 229 | notifier.timerActive = 0; |
| 230 | } else { |
| 231 | /* |
| 232 | * Compute when the timer should fire. |
| 233 | */ |
| 234 | |
| 235 | TclpGetTime(¬ifier.timer); |
| 236 | notifier.timer.sec += timePtr->sec; |
| 237 | notifier.timer.usec += timePtr->usec; |
| 238 | if (notifier.timer.usec >= 1000000) { |
| 239 | notifier.timer.usec -= 1000000; |
| 240 | notifier.timer.sec += 1; |
| 241 | } |
| 242 | notifier.timerActive = 1; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /* |
| 247 | *---------------------------------------------------------------------- |
| 248 | * |
| 249 | * Tcl_WaitForEvent -- |
| 250 | * |
| 251 | * This function is called by Tcl_DoOneEvent to wait for new |
| 252 | * events on the message queue. If the block time is 0, then |
| 253 | * Tcl_WaitForEvent just polls the event queue without blocking. |
| 254 | * |
| 255 | * Results: |
| 256 | * Always returns 0. |
| 257 | * |
| 258 | * Side effects: |
| 259 | * None. |
| 260 | * |
| 261 | *---------------------------------------------------------------------- |
| 262 | */ |
| 263 | |
| 264 | int |
| 265 | Tcl_WaitForEvent( |
| 266 | Tcl_Time *timePtr) /* Maximum block time. */ |
| 267 | { |
| 268 | int found; |
| 269 | EventRecord macEvent; |
| 270 | long sleepTime = 5; |
| 271 | long ms; |
| 272 | Point currentMouse; |
| 273 | void * timerToken; |
| 274 | Rect mouseRect; |
| 275 | |
| 276 | /* |
| 277 | * Compute the next timeout value. |
| 278 | */ |
| 279 | |
| 280 | if (!timePtr) { |
| 281 | ms = INT_MAX; |
| 282 | } else { |
| 283 | ms = (timePtr->sec * 1000) + (timePtr->usec / 1000); |
| 284 | } |
| 285 | timerToken = TclMacStartTimer((long) ms); |
| 286 | |
| 287 | /* |
| 288 | * Poll the Mac event sources. This loop repeats until something |
| 289 | * happens: a timeout, a socket event, mouse motion, or some other |
| 290 | * window event. Note that we don't call WaitNextEvent if another |
| 291 | * event is found to avoid context switches. This effectively gives |
| 292 | * events coming in via WaitNextEvent a slightly lower priority. |
| 293 | */ |
| 294 | |
| 295 | found = 0; |
| 296 | if (notifier.utilityRgn == NULL) { |
| 297 | notifier.utilityRgn = NewRgn(); |
| 298 | } |
| 299 | |
| 300 | while (!found) { |
| 301 | /* |
| 302 | * Check for generated and queued events. |
| 303 | */ |
| 304 | |
| 305 | if (HandleMacEvents()) { |
| 306 | found = 1; |
| 307 | } |
| 308 | |
| 309 | /* |
| 310 | * Check for time out. |
| 311 | */ |
| 312 | |
| 313 | if (!found && TclMacTimerExpired(timerToken)) { |
| 314 | found = 1; |
| 315 | } |
| 316 | |
| 317 | /* |
| 318 | * Mod by Jack: poll for select() events. Code is in TclSelectNotify.c |
| 319 | */ |
| 320 | { |
| 321 | int Tcl_PollSelectEvent(void); |
| 322 | if (!found && Tcl_PollSelectEvent()) |
| 323 | found = 1; |
| 324 | } |
| 325 | |
| 326 | /* |
| 327 | * Check for window events. We may receive a NULL event for |
| 328 | * various reasons. 1) the timer has expired, 2) a mouse moved |
| 329 | * event is occuring or 3) the os is giving us time for idle |
| 330 | * events. Note that we aren't sharing the processor very |
| 331 | * well here. We really ought to do a better job of calling |
| 332 | * WaitNextEvent for time slicing purposes. |
| 333 | */ |
| 334 | |
| 335 | if (!found) { |
| 336 | /* |
| 337 | * Set up mouse region so we will wake if the mouse is moved. |
| 338 | * We do this by defining the smallest possible region around |
| 339 | * the current mouse position. |
| 340 | */ |
| 341 | |
| 342 | GetGlobalMouse(¤tMouse); |
| 343 | SetRect(&mouseRect, currentMouse.h, currentMouse.v, |
| 344 | currentMouse.h + 1, currentMouse.v + 1); |
| 345 | RectRgn(notifier.utilityRgn, &mouseRect); |
| 346 | |
| 347 | WaitNextEvent(everyEvent, &macEvent, sleepTime, |
| 348 | notifier.utilityRgn); |
| 349 | |
| 350 | if (notifier.eventProcPtr != NULL) { |
| 351 | if ((*notifier.eventProcPtr)(&macEvent) == true) { |
| 352 | found = 1; |
| 353 | } |
| 354 | } |
| 355 | } |
| 356 | } |
| 357 | TclMacRemoveTimer(timerToken); |
| 358 | return 0; |
| 359 | } |
| 360 | |
| 361 | /* |
| 362 | *---------------------------------------------------------------------- |
| 363 | * |
| 364 | * Tcl_Sleep -- |
| 365 | * |
| 366 | * Delay execution for the specified number of milliseconds. This |
| 367 | * is not a very good call to make. It will block the system - |
| 368 | * you will not even be able to switch applications. |
| 369 | * |
| 370 | * Results: |
| 371 | * None. |
| 372 | * |
| 373 | * Side effects: |
| 374 | * Time passes. |
| 375 | * |
| 376 | *---------------------------------------------------------------------- |
| 377 | */ |
| 378 | |
| 379 | void |
| 380 | Tcl_Sleep( |
| 381 | int ms) /* Number of milliseconds to sleep. */ |
| 382 | { |
| 383 | EventRecord dummy; |
| 384 | void *timerToken; |
| 385 | |
| 386 | if (ms <= 0) { |
| 387 | return; |
| 388 | } |
| 389 | |
| 390 | timerToken = TclMacStartTimer((long) ms); |
| 391 | while (1) { |
| 392 | WaitNextEvent(0, &dummy, (ms / 16.66) + 1, NULL); |
| 393 | |
| 394 | if (TclMacTimerExpired(timerToken)) { |
| 395 | break; |
| 396 | } |
| 397 | } |
| 398 | TclMacRemoveTimer(timerToken); |
| 399 | } |
| 400 | |
| 401 | /* |
| 402 | *---------------------------------------------------------------------- |
| 403 | * |
| 404 | * Tcl_MacSetEventProc -- |
| 405 | * |
| 406 | * This function sets the event handling procedure for the |
| 407 | * application. This function will be passed all incoming Mac |
| 408 | * events. This function usually controls the console or some |
| 409 | * other entity like Tk. |
| 410 | * |
| 411 | * Results: |
| 412 | * None. |
| 413 | * |
| 414 | * Side effects: |
| 415 | * Changes the event handling function. |
| 416 | * |
| 417 | *---------------------------------------------------------------------- |
| 418 | */ |
| 419 | |
| 420 | void |
| 421 | Tcl_MacSetEventProc( |
| 422 | Tcl_MacConvertEventPtr procPtr) |
| 423 | { |
| 424 | notifier.eventProcPtr = procPtr; |
| 425 | } |