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