1/* 2 * tkPointer.c -- 3 * 4 * This file contains functions for emulating the X server pointer and 5 * grab state machine. This file is used by the Mac and Windows platforms 6 * to generate appropriate enter/leave events, and to update the global 7 * grab window information. 8 * 9 * Copyright (c) 1996 by Sun Microsystems, Inc. 10 * 11 * See the file "license.terms" for information on usage and redistribution of 12 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id$ 15 */ 16 17#include "tkInt.h" 18 19#ifdef __WIN32__ 20#include "tkWinInt.h" 21#endif 22 23#if defined(MAC_OSX_TK) 24#include "tkMacOSXInt.h" 25#define Cursor XCursor 26#endif 27 28/* 29 * Mask that selects any of the state bits corresponding to buttons, plus 30 * masks that select individual buttons' bits: 31 */ 32 33#define ALL_BUTTONS \ 34 (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask) 35static unsigned int buttonMasks[] = { 36 Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask 37}; 38#define ButtonMask(b) (buttonMasks[(b)-Button1]) 39 40typedef struct ThreadSpecificData { 41 TkWindow *grabWinPtr; /* Window that defines the top of the grab 42 * tree in a global grab. */ 43 int lastState; /* Last known state flags. */ 44 XPoint lastPos; /* Last reported mouse position. */ 45 TkWindow *lastWinPtr; /* Last reported mouse window. */ 46 TkWindow *restrictWinPtr; /* Window to which all mouse events will be 47 * reported. */ 48 TkWindow *cursorWinPtr; /* Window that is currently controlling the 49 * global cursor. */ 50} ThreadSpecificData; 51static Tcl_ThreadDataKey dataKey; 52 53/* 54 * Forward declarations of procedures used in this file. 55 */ 56 57static int GenerateEnterLeave(TkWindow *winPtr, int x, int y, 58 int state); 59static void InitializeEvent(XEvent* eventPtr, TkWindow *winPtr, 60 int type, int x, int y, int state, int detail); 61static void UpdateCursor(TkWindow *winPtr); 62 63/* 64 *---------------------------------------------------------------------- 65 * 66 * InitializeEvent -- 67 * 68 * Initializes the common fields for several X events. 69 * 70 * Results: 71 * None. 72 * 73 * Side effects: 74 * Fills in the specified event structure. 75 * 76 *---------------------------------------------------------------------- 77 */ 78 79static void 80InitializeEvent( 81 XEvent *eventPtr, /* Event structure to initialize. */ 82 TkWindow *winPtr, /* Window to make event relative to. */ 83 int type, /* Message type. */ 84 int x, int y, /* Root coords of event. */ 85 int state, /* State flags. */ 86 int detail) /* Detail value. */ 87{ 88 eventPtr->type = type; 89 eventPtr->xany.serial = LastKnownRequestProcessed(winPtr->display); 90 eventPtr->xany.send_event = False; 91 eventPtr->xany.display = winPtr->display; 92 93 eventPtr->xcrossing.root = RootWindow(winPtr->display, winPtr->screenNum); 94 eventPtr->xcrossing.time = TkpGetMS(); 95 eventPtr->xcrossing.x_root = x; 96 eventPtr->xcrossing.y_root = y; 97 98 switch (type) { 99 case EnterNotify: 100 case LeaveNotify: 101 eventPtr->xcrossing.mode = NotifyNormal; 102 eventPtr->xcrossing.state = state; 103 eventPtr->xcrossing.detail = detail; 104 eventPtr->xcrossing.focus = False; 105 break; 106 case MotionNotify: 107 eventPtr->xmotion.state = state; 108 eventPtr->xmotion.is_hint = detail; 109 break; 110 case ButtonPress: 111 case ButtonRelease: 112 eventPtr->xbutton.state = state; 113 eventPtr->xbutton.button = detail; 114 break; 115 } 116 TkChangeEventWindow(eventPtr, winPtr); 117} 118 119/* 120 *---------------------------------------------------------------------- 121 * 122 * GenerateEnterLeave -- 123 * 124 * Update the current mouse window and position, and generate any 125 * enter/leave events that are needed. 126 * 127 * Results: 128 * Returns 1 if enter/leave events were generated. 129 * 130 * Side effects: 131 * May insert events into the Tk event queue. 132 * 133 *---------------------------------------------------------------------- 134 */ 135 136static int 137GenerateEnterLeave( 138 TkWindow *winPtr, /* Current Tk window (or NULL). */ 139 int x, int y, /* Current mouse position in root coords. */ 140 int state) /* State flags. */ 141{ 142 int crossed = 0; /* 1 if mouse crossed a window boundary */ 143 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 144 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 145 TkWindow *restrictWinPtr = tsdPtr->restrictWinPtr; 146 TkWindow *lastWinPtr = tsdPtr->lastWinPtr; 147 148 if (winPtr != tsdPtr->lastWinPtr) { 149 if (restrictWinPtr) { 150 int newPos, oldPos; 151 152 newPos = TkPositionInTree(winPtr, restrictWinPtr); 153 oldPos = TkPositionInTree(lastWinPtr, restrictWinPtr); 154 155 /* 156 * Check if the mouse crossed into or out of the restrict window. 157 * If so, we need to generate an Enter or Leave event. 158 */ 159 160 if ((newPos != oldPos) && ((newPos == TK_GRAB_IN_TREE) 161 || (oldPos == TK_GRAB_IN_TREE))) { 162 XEvent event; 163 int type, detail; 164 165 if (newPos == TK_GRAB_IN_TREE) { 166 type = EnterNotify; 167 } else { 168 type = LeaveNotify; 169 } 170 if ((oldPos == TK_GRAB_ANCESTOR) 171 || (newPos == TK_GRAB_ANCESTOR)) { 172 detail = NotifyAncestor; 173 } else { 174 detail = NotifyVirtual; 175 } 176 InitializeEvent(&event, restrictWinPtr, type, x, y, 177 state, detail); 178 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 179 } 180 181 } else { 182 TkWindow *targetPtr; 183 184 if ((lastWinPtr == NULL) 185 || (lastWinPtr->window == None)) { 186 targetPtr = winPtr; 187 } else { 188 targetPtr = lastWinPtr; 189 } 190 191 if (targetPtr && (targetPtr->window != None)) { 192 XEvent event; 193 194 /* 195 * Generate appropriate Enter/Leave events. 196 */ 197 198 InitializeEvent(&event, targetPtr, LeaveNotify, x, y, state, 199 NotifyNormal); 200 201 TkInOutEvents(&event, lastWinPtr, winPtr, LeaveNotify, 202 EnterNotify, TCL_QUEUE_TAIL); 203 crossed = 1; 204 } 205 } 206 tsdPtr->lastWinPtr = winPtr; 207 } 208 209 return crossed; 210} 211 212/* 213 *---------------------------------------------------------------------- 214 * 215 * Tk_UpdatePointer -- 216 * 217 * This function updates the pointer state machine given an the current 218 * window, position and modifier state. 219 * 220 * Results: 221 * None. 222 * 223 * Side effects: 224 * May queue new events and update the grab state. 225 * 226 *---------------------------------------------------------------------- 227 */ 228 229void 230Tk_UpdatePointer( 231 Tk_Window tkwin, /* Window to which pointer event is reported. 232 * May be NULL. */ 233 int x, int y, /* Pointer location in root coords. */ 234 int state) /* Modifier state mask. */ 235{ 236 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 237 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 238 TkWindow *winPtr = (TkWindow *)tkwin; 239 TkWindow *targetWinPtr; 240 XPoint pos; 241 XEvent event; 242 int changes = (state ^ tsdPtr->lastState) & ALL_BUTTONS; 243 int type, b, mask; 244 245 pos.x = x; 246 pos.y = y; 247 248 /* 249 * Use the current keyboard state, but the old mouse button state since we 250 * haven't generated the button events yet. 251 */ 252 253 tsdPtr->lastState = (state & ~ALL_BUTTONS) | (tsdPtr->lastState 254 & ALL_BUTTONS); 255 256 /* 257 * Generate Enter/Leave events. If the pointer has crossed window 258 * boundaries, update the current mouse position so we don't generate 259 * redundant motion events. 260 */ 261 262 if (GenerateEnterLeave(winPtr, x, y, tsdPtr->lastState)) { 263 tsdPtr->lastPos = pos; 264 } 265 266 /* 267 * Generate ButtonPress/ButtonRelease events based on the differences 268 * between the current button state and the last known button state. 269 */ 270 271 for (b = Button1; b <= Button5; b++) { 272 mask = ButtonMask(b); 273 if (changes & mask) { 274 if (state & mask) { 275 type = ButtonPress; 276 277 /* 278 * ButtonPress - Set restrict window if we aren't grabbed, or 279 * if this is the first button down. 280 */ 281 282 if (!tsdPtr->restrictWinPtr) { 283 if (!tsdPtr->grabWinPtr) { 284 /* 285 * Mouse is not grabbed, so set a button grab. 286 */ 287 288 tsdPtr->restrictWinPtr = winPtr; 289 TkpSetCapture(tsdPtr->restrictWinPtr); 290 291 } else if ((tsdPtr->lastState & ALL_BUTTONS) == 0) { 292 /* 293 * Mouse is in a non-button grab, so ensure the button 294 * grab is inside the grab tree. 295 */ 296 297 if (TkPositionInTree(winPtr, tsdPtr->grabWinPtr) 298 == TK_GRAB_IN_TREE) { 299 tsdPtr->restrictWinPtr = winPtr; 300 } else { 301 tsdPtr->restrictWinPtr = tsdPtr->grabWinPtr; 302 } 303 TkpSetCapture(tsdPtr->restrictWinPtr); 304 } 305 } 306 307 } else { 308 type = ButtonRelease; 309 310 /* 311 * ButtonRelease - Release the mouse capture and clear the 312 * restrict window when the last button is released. If we 313 * are in a global grab, restore the grab window capture. 314 */ 315 316 if ((tsdPtr->lastState & ALL_BUTTONS) == mask) { 317 TkpSetCapture(tsdPtr->grabWinPtr); 318 } 319 320 /* 321 * If we are releasing a restrict window, then we need to send 322 * the button event followed by mouse motion from the restrict 323 * window to the current mouse position. 324 */ 325 326 if (tsdPtr->restrictWinPtr) { 327 InitializeEvent(&event, tsdPtr->restrictWinPtr, type, x, y, 328 tsdPtr->lastState, b); 329 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 330 tsdPtr->lastState &= ~mask; 331 tsdPtr->lastWinPtr = tsdPtr->restrictWinPtr; 332 tsdPtr->restrictWinPtr = NULL; 333 334 GenerateEnterLeave(winPtr, x, y, tsdPtr->lastState); 335 tsdPtr->lastPos = pos; 336 continue; 337 } 338 } 339 340 /* 341 * If a restrict window is set, make sure the pointer event is 342 * reported relative to that window. Otherwise, if a global grab 343 * is in effect then events outside of windows managed by Tk 344 * should be reported to the grab window. 345 */ 346 347 if (tsdPtr->restrictWinPtr) { 348 targetWinPtr = tsdPtr->restrictWinPtr; 349 } else if (tsdPtr->grabWinPtr && !winPtr) { 350 targetWinPtr = tsdPtr->grabWinPtr; 351 } else { 352 targetWinPtr = winPtr; 353 } 354 355 /* 356 * If we still have a target window, send the event. 357 */ 358 359 if (targetWinPtr != NULL) { 360 InitializeEvent(&event, targetWinPtr, type, x, y, 361 tsdPtr->lastState, b); 362 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 363 } 364 365 /* 366 * Update the state for the next iteration. 367 */ 368 369 tsdPtr->lastState = (type == ButtonPress) 370 ? (tsdPtr->lastState | mask) : (tsdPtr->lastState & ~mask); 371 tsdPtr->lastPos = pos; 372 } 373 } 374 375 /* 376 * Make sure the cursor window is up to date. 377 */ 378 379 if (tsdPtr->restrictWinPtr) { 380 targetWinPtr = tsdPtr->restrictWinPtr; 381 } else if (tsdPtr->grabWinPtr) { 382 targetWinPtr = (TkPositionInTree(winPtr, tsdPtr->grabWinPtr) 383 == TK_GRAB_IN_TREE) ? winPtr : tsdPtr->grabWinPtr; 384 } else { 385 targetWinPtr = winPtr; 386 } 387 UpdateCursor(targetWinPtr); 388 389 /* 390 * If no other events caused the position to be updated, generate a motion 391 * event. 392 */ 393 394 if (tsdPtr->lastPos.x != pos.x || tsdPtr->lastPos.y != pos.y) { 395 if (tsdPtr->restrictWinPtr) { 396 targetWinPtr = tsdPtr->restrictWinPtr; 397 } else if (tsdPtr->grabWinPtr && !winPtr) { 398 targetWinPtr = tsdPtr->grabWinPtr; 399 } 400 401 if (targetWinPtr != NULL) { 402 InitializeEvent(&event, targetWinPtr, MotionNotify, x, y, 403 tsdPtr->lastState, NotifyNormal); 404 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); 405 } 406 tsdPtr->lastPos = pos; 407 } 408} 409 410/* 411 *---------------------------------------------------------------------- 412 * 413 * XGrabPointer -- 414 * 415 * Capture the mouse so event are reported outside of toplevels. Note 416 * that this is a very limited implementation that only supports 417 * GrabModeAsync and owner_events True. 418 * 419 * Results: 420 * Always returns GrabSuccess. 421 * 422 * Side effects: 423 * Turns on mouse capture, sets the global grab pointer, and clears any 424 * window restrictions. 425 * 426 *---------------------------------------------------------------------- 427 */ 428 429int 430XGrabPointer( 431 Display *display, 432 Window grab_window, 433 Bool owner_events, 434 unsigned int event_mask, 435 int pointer_mode, 436 int keyboard_mode, 437 Window confine_to, 438 Cursor cursor, 439 Time time) 440{ 441 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 442 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 443 444 display->request++; 445 tsdPtr->grabWinPtr = (TkWindow *) Tk_IdToWindow(display, grab_window); 446 tsdPtr->restrictWinPtr = NULL; 447 TkpSetCapture(tsdPtr->grabWinPtr); 448 if (TkPositionInTree(tsdPtr->lastWinPtr, tsdPtr->grabWinPtr) 449 != TK_GRAB_IN_TREE) { 450 UpdateCursor(tsdPtr->grabWinPtr); 451 } 452 return GrabSuccess; 453} 454 455/* 456 *---------------------------------------------------------------------- 457 * 458 * XUngrabPointer -- 459 * 460 * Release the current grab. 461 * 462 * Results: 463 * None. 464 * 465 * Side effects: 466 * Releases the mouse capture. 467 * 468 *---------------------------------------------------------------------- 469 */ 470 471void 472XUngrabPointer( 473 Display *display, 474 Time time) 475{ 476 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 477 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 478 479 display->request++; 480 tsdPtr->grabWinPtr = NULL; 481 tsdPtr->restrictWinPtr = NULL; 482 TkpSetCapture(NULL); 483 UpdateCursor(tsdPtr->lastWinPtr); 484} 485 486/* 487 *---------------------------------------------------------------------- 488 * 489 * TkPointerDeadWindow -- 490 * 491 * Clean up pointer module state when a window is destroyed. 492 * 493 * Results: 494 * None. 495 * 496 * Side effects: 497 * May release the current capture window. 498 * 499 *---------------------------------------------------------------------- 500 */ 501 502void 503TkPointerDeadWindow( 504 TkWindow *winPtr) 505{ 506 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 507 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 508 509 if (winPtr == tsdPtr->lastWinPtr) { 510 tsdPtr->lastWinPtr = NULL; 511 } 512 if (winPtr == tsdPtr->grabWinPtr) { 513 tsdPtr->grabWinPtr = NULL; 514 } 515 if (winPtr == tsdPtr->restrictWinPtr) { 516 tsdPtr->restrictWinPtr = NULL; 517 } 518 if (!(tsdPtr->restrictWinPtr || tsdPtr->grabWinPtr)) { 519 TkpSetCapture(NULL); 520 } 521} 522 523/* 524 *---------------------------------------------------------------------- 525 * 526 * UpdateCursor -- 527 * 528 * Set the windows global cursor to the cursor associated with the given 529 * Tk window. 530 * 531 * Results: 532 * None. 533 * 534 * Side effects: 535 * Changes the mouse cursor. 536 * 537 *---------------------------------------------------------------------- 538 */ 539 540static void 541UpdateCursor( 542 TkWindow *winPtr) 543{ 544 Cursor cursor = None; 545 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 546 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 547 548 /* 549 * A window inherits its cursor from its parent if it doesn't have one of 550 * its own. Top level windows inherit the default cursor. 551 */ 552 553 tsdPtr->cursorWinPtr = winPtr; 554 while (winPtr != NULL) { 555 if (winPtr->atts.cursor != None) { 556 cursor = winPtr->atts.cursor; 557 break; 558 } else if (winPtr->flags & TK_TOP_HIERARCHY) { 559 break; 560 } 561 winPtr = winPtr->parentPtr; 562 } 563 TkpSetCursor((TkpCursor) cursor); 564} 565 566/* 567 *---------------------------------------------------------------------- 568 * 569 * XDefineCursor -- 570 * 571 * This function is called to update the cursor on a window. Since the 572 * mouse might be in the specified window, we need to check the specified 573 * window against the current mouse position and grab state. 574 * 575 * Results: 576 * None. 577 * 578 * Side effects: 579 * May update the cursor. 580 * 581 *---------------------------------------------------------------------- 582 */ 583 584void 585XDefineCursor( 586 Display *display, 587 Window w, 588 Cursor cursor) 589{ 590 TkWindow *winPtr = (TkWindow *)Tk_IdToWindow(display, w); 591 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 592 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 593 594 if (tsdPtr->cursorWinPtr == winPtr) { 595 UpdateCursor(winPtr); 596 } 597 display->request++; 598} 599 600/* 601 *---------------------------------------------------------------------- 602 * 603 * TkGenerateActivateEvents -- 604 * 605 * This function is called by the Mac and Windows window manager routines 606 * when a toplevel window is activated or deactivated. 607 * Activate/Deactivate events will be sent to every subwindow of the 608 * toplevel followed by a FocusIn/FocusOut message. 609 * 610 * Results: 611 * None. 612 * 613 * Side effects: 614 * Generates X events. 615 * 616 *---------------------------------------------------------------------- 617 */ 618 619void 620TkGenerateActivateEvents( 621 TkWindow *winPtr, /* Toplevel to activate. */ 622 int active) /* Non-zero if the window is being activated, 623 * else 0.*/ 624{ 625 XEvent event; 626 627 /* 628 * Generate Activate and Deactivate events. This event is sent to every 629 * subwindow in a toplevel window. 630 */ 631 632 event.xany.serial = winPtr->display->request++; 633 event.xany.send_event = False; 634 event.xany.display = winPtr->display; 635 event.xany.window = winPtr->window; 636 637 event.xany.type = active ? ActivateNotify : DeactivateNotify; 638 TkQueueEventForAllChildren(winPtr, &event); 639} 640 641/* 642 * Local Variables: 643 * mode: c 644 * c-basic-offset: 4 645 * fill-column: 78 646 * End: 647 */ 648