/* * tkPointer.c -- * * This file contains functions for emulating the X server * pointer and grab state machine. This file is used by the * Mac and Windows platforms to generate appropriate enter/leave * events, and to update the global grab window information. * * Copyright (c) 1996 by Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkPointer.c,v 1.1.1.1 2007/07/10 15:05:17 duncan Exp $ */ #include "tkInt.h" #ifdef __WIN32__ #include "tkWinInt.h" #endif #if defined(MAC_TCL) #include "tkMacInt.h" #define Cursor XCursor #endif #if defined(MAC_OSX_TK) #include "tkMacOSXInt.h" #define Cursor XCursor #endif /* * Mask that selects any of the state bits corresponding to buttons, * plus masks that select individual buttons' bits: */ #define ALL_BUTTONS \ (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask) static unsigned int buttonMasks[] = { Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask }; #define ButtonMask(b) (buttonMasks[(b)-Button1]) typedef struct ThreadSpecificData { TkWindow *grabWinPtr; /* Window that defines the top of the * grab tree in a global grab. */ int lastState; /* Last known state flags. */ XPoint lastPos; /* Last reported mouse position. */ TkWindow *lastWinPtr; /* Last reported mouse window. */ TkWindow *restrictWinPtr; /* Window to which all mouse events * will be reported. */ TkWindow *cursorWinPtr; /* Window that is currently * controlling the global cursor. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Forward declarations of procedures used in this file. */ static int GenerateEnterLeave _ANSI_ARGS_((TkWindow *winPtr, int x, int y, int state)); static void InitializeEvent _ANSI_ARGS_((XEvent* eventPtr, TkWindow *winPtr, int type, int x, int y, int state, int detail)); static void UpdateCursor _ANSI_ARGS_((TkWindow *winPtr)); /* *---------------------------------------------------------------------- * * InitializeEvent -- * * Initializes the common fields for several X events. * * Results: * None. * * Side effects: * Fills in the specified event structure. * *---------------------------------------------------------------------- */ static void InitializeEvent(eventPtr, winPtr, type, x, y, state, detail) XEvent* eventPtr; /* Event structure to initialize. */ TkWindow *winPtr; /* Window to make event relative to. */ int type; /* Message type. */ int x, y; /* Root coords of event. */ int state; /* State flags. */ int detail; /* Detail value. */ { eventPtr->type = type; eventPtr->xany.serial = LastKnownRequestProcessed(winPtr->display); eventPtr->xany.send_event = False; eventPtr->xany.display = winPtr->display; eventPtr->xcrossing.root = RootWindow(winPtr->display, winPtr->screenNum); eventPtr->xcrossing.time = TkpGetMS(); eventPtr->xcrossing.x_root = x; eventPtr->xcrossing.y_root = y; switch (type) { case EnterNotify: case LeaveNotify: eventPtr->xcrossing.mode = NotifyNormal; eventPtr->xcrossing.state = state; eventPtr->xcrossing.detail = detail; eventPtr->xcrossing.focus = False; break; case MotionNotify: eventPtr->xmotion.state = state; eventPtr->xmotion.is_hint = detail; break; case ButtonPress: case ButtonRelease: eventPtr->xbutton.state = state; eventPtr->xbutton.button = detail; break; } TkChangeEventWindow(eventPtr, winPtr); } /* *---------------------------------------------------------------------- * * GenerateEnterLeave -- * * Update the current mouse window and position, and generate * any enter/leave events that are needed. * * Results: * Returns 1 if enter/leave events were generated. * * Side effects: * May insert events into the Tk event queue. * *---------------------------------------------------------------------- */ static int GenerateEnterLeave(winPtr, x, y, state) TkWindow *winPtr; /* Current Tk window (or NULL). */ int x,y; /* Current mouse position in root coords. */ int state; /* State flags. */ { int crossed = 0; /* 1 if mouse crossed a window boundary */ ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); TkWindow *restrictWinPtr = tsdPtr->restrictWinPtr; TkWindow *lastWinPtr = tsdPtr->lastWinPtr; if (winPtr != tsdPtr->lastWinPtr) { if (restrictWinPtr) { int newPos, oldPos; newPos = TkPositionInTree(winPtr, restrictWinPtr); oldPos = TkPositionInTree(lastWinPtr, restrictWinPtr); /* * Check if the mouse crossed into or out of the restrict * window. If so, we need to generate an Enter or Leave event. */ if ((newPos != oldPos) && ((newPos == TK_GRAB_IN_TREE) || (oldPos == TK_GRAB_IN_TREE))) { XEvent event; int type, detail; if (newPos == TK_GRAB_IN_TREE) { type = EnterNotify; } else { type = LeaveNotify; } if ((oldPos == TK_GRAB_ANCESTOR) || (newPos == TK_GRAB_ANCESTOR)) { detail = NotifyAncestor; } else { detail = NotifyVirtual; } InitializeEvent(&event, restrictWinPtr, type, x, y, state, detail); Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); } } else { TkWindow *targetPtr; if ((lastWinPtr == NULL) || (lastWinPtr->window == None)) { targetPtr = winPtr; } else { targetPtr = lastWinPtr; } if (targetPtr && (targetPtr->window != None)) { XEvent event; /* * Generate appropriate Enter/Leave events. */ InitializeEvent(&event, targetPtr, LeaveNotify, x, y, state, NotifyNormal); TkInOutEvents(&event, lastWinPtr, winPtr, LeaveNotify, EnterNotify, TCL_QUEUE_TAIL); crossed = 1; } } tsdPtr->lastWinPtr = winPtr; } return crossed; } /* *---------------------------------------------------------------------- * * Tk_UpdatePointer -- * * This function updates the pointer state machine given an * the current window, position and modifier state. * * Results: * None. * * Side effects: * May queue new events and update the grab state. * *---------------------------------------------------------------------- */ void Tk_UpdatePointer(tkwin, x, y, state) Tk_Window tkwin; /* Window to which pointer event * is reported. May be NULL. */ int x, y; /* Pointer location in root coords. */ int state; /* Modifier state mask. */ { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); TkWindow *winPtr = (TkWindow *)tkwin; TkWindow *targetWinPtr; XPoint pos; XEvent event; int changes = (state ^ tsdPtr->lastState) & ALL_BUTTONS; int type, b, mask; pos.x = x; pos.y = y; /* * Use the current keyboard state, but the old mouse button * state since we haven't generated the button events yet. */ tsdPtr->lastState = (state & ~ALL_BUTTONS) | (tsdPtr->lastState & ALL_BUTTONS); /* * Generate Enter/Leave events. If the pointer has crossed window * boundaries, update the current mouse position so we don't generate * redundant motion events. */ if (GenerateEnterLeave(winPtr, x, y, tsdPtr->lastState)) { tsdPtr->lastPos = pos; } /* * Generate ButtonPress/ButtonRelease events based on the differences * between the current button state and the last known button state. */ for (b = Button1; b <= Button3; b++) { mask = ButtonMask(b); if (changes & mask) { if (state & mask) { type = ButtonPress; /* * ButtonPress - Set restrict window if we aren't grabbed, or * if this is the first button down. */ if (!tsdPtr->restrictWinPtr) { if (!tsdPtr->grabWinPtr) { /* * Mouse is not grabbed, so set a button grab. */ tsdPtr->restrictWinPtr = winPtr; TkpSetCapture(tsdPtr->restrictWinPtr); } else if ((tsdPtr->lastState & ALL_BUTTONS) == 0) { /* * Mouse is in a non-button grab, so ensure * the button grab is inside the grab tree. */ if (TkPositionInTree(winPtr, tsdPtr->grabWinPtr) == TK_GRAB_IN_TREE) { tsdPtr->restrictWinPtr = winPtr; } else { tsdPtr->restrictWinPtr = tsdPtr->grabWinPtr; } TkpSetCapture(tsdPtr->restrictWinPtr); } } } else { type = ButtonRelease; /* * ButtonRelease - Release the mouse capture and clear the * restrict window when the last button is released and we * aren't in a global grab. */ if ((tsdPtr->lastState & ALL_BUTTONS) == mask) { if (!tsdPtr->grabWinPtr) { TkpSetCapture(NULL); } } /* * If we are releasing a restrict window, then we need * to send the button event followed by mouse motion from * the restrict window to the current mouse position. */ if (tsdPtr->restrictWinPtr) { InitializeEvent(&event, tsdPtr->restrictWinPtr, type, x, y, tsdPtr->lastState, b); Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); tsdPtr->lastState &= ~mask; tsdPtr->lastWinPtr = tsdPtr->restrictWinPtr; tsdPtr->restrictWinPtr = NULL; GenerateEnterLeave(winPtr, x, y, tsdPtr->lastState); tsdPtr->lastPos = pos; continue; } } /* * If a restrict window is set, make sure the pointer event * is reported relative to that window. Otherwise, if a * global grab is in effect then events outside of windows * managed by Tk should be reported to the grab window. */ if (tsdPtr->restrictWinPtr) { targetWinPtr = tsdPtr->restrictWinPtr; } else if (tsdPtr->grabWinPtr && !winPtr) { targetWinPtr = tsdPtr->grabWinPtr; } else { targetWinPtr = winPtr; } /* * If we still have a target window, send the event. */ if (winPtr != NULL) { InitializeEvent(&event, targetWinPtr, type, x, y, tsdPtr->lastState, b); Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); } /* * Update the state for the next iteration. */ tsdPtr->lastState = (type == ButtonPress) ? (tsdPtr->lastState | mask) : (tsdPtr->lastState & ~mask); tsdPtr->lastPos = pos; } } /* * Make sure the cursor window is up to date. */ if (tsdPtr->restrictWinPtr) { targetWinPtr = tsdPtr->restrictWinPtr; } else if (tsdPtr->grabWinPtr) { targetWinPtr = (TkPositionInTree(winPtr, tsdPtr->grabWinPtr) == TK_GRAB_IN_TREE) ? winPtr : tsdPtr->grabWinPtr; } else { targetWinPtr = winPtr; } UpdateCursor(targetWinPtr); /* * If no other events caused the position to be updated, * generate a motion event. */ if (tsdPtr->lastPos.x != pos.x || tsdPtr->lastPos.y != pos.y) { if (tsdPtr->restrictWinPtr) { targetWinPtr = tsdPtr->restrictWinPtr; } else if (tsdPtr->grabWinPtr && !winPtr) { targetWinPtr = tsdPtr->grabWinPtr; } if (targetWinPtr != NULL) { InitializeEvent(&event, targetWinPtr, MotionNotify, x, y, tsdPtr->lastState, NotifyNormal); Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); } tsdPtr->lastPos = pos; } } /* *---------------------------------------------------------------------- * * XGrabPointer -- * * Capture the mouse so event are reported outside of toplevels. * Note that this is a very limited implementation that only * supports GrabModeAsync and owner_events True. * * Results: * Always returns GrabSuccess. * * Side effects: * Turns on mouse capture, sets the global grab pointer, and * clears any window restrictions. * *---------------------------------------------------------------------- */ int XGrabPointer(display, grab_window, owner_events, event_mask, pointer_mode, keyboard_mode, confine_to, cursor, time) Display* display; Window grab_window; Bool owner_events; unsigned int event_mask; int pointer_mode; int keyboard_mode; Window confine_to; Cursor cursor; Time time; { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); display->request++; tsdPtr->grabWinPtr = (TkWindow *) Tk_IdToWindow(display, grab_window); tsdPtr->restrictWinPtr = NULL; TkpSetCapture(tsdPtr->grabWinPtr); if (TkPositionInTree(tsdPtr->lastWinPtr, tsdPtr->grabWinPtr) != TK_GRAB_IN_TREE) { UpdateCursor(tsdPtr->grabWinPtr); } return GrabSuccess; } /* *---------------------------------------------------------------------- * * XUngrabPointer -- * * Release the current grab. * * Results: * None. * * Side effects: * Releases the mouse capture. * *---------------------------------------------------------------------- */ void XUngrabPointer(display, time) Display* display; Time time; { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); display->request++; tsdPtr->grabWinPtr = NULL; tsdPtr->restrictWinPtr = NULL; TkpSetCapture(NULL); UpdateCursor(tsdPtr->lastWinPtr); } /* *---------------------------------------------------------------------- * * TkPointerDeadWindow -- * * Clean up pointer module state when a window is destroyed. * * Results: * None. * * Side effects: * May release the current capture window. * *---------------------------------------------------------------------- */ void TkPointerDeadWindow(winPtr) TkWindow *winPtr; { ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); if (winPtr == tsdPtr->lastWinPtr) { tsdPtr->lastWinPtr = NULL; } if (winPtr == tsdPtr->grabWinPtr) { tsdPtr->grabWinPtr = NULL; } if (winPtr == tsdPtr->restrictWinPtr) { tsdPtr->restrictWinPtr = NULL; } if (!(tsdPtr->restrictWinPtr || tsdPtr->grabWinPtr)) { TkpSetCapture(NULL); } } /* *---------------------------------------------------------------------- * * UpdateCursor -- * * Set the windows global cursor to the cursor associated with * the given Tk window. * * Results: * None. * * Side effects: * Changes the mouse cursor. * *---------------------------------------------------------------------- */ static void UpdateCursor(winPtr) TkWindow *winPtr; { Cursor cursor = None; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); /* * A window inherits its cursor from its parent if it doesn't * have one of its own. Top level windows inherit the default * cursor. */ tsdPtr->cursorWinPtr = winPtr; while (winPtr != NULL) { if (winPtr->atts.cursor != None) { cursor = winPtr->atts.cursor; break; } else if (winPtr->flags & TK_TOP_HIERARCHY) { break; } winPtr = winPtr->parentPtr; } TkpSetCursor((TkpCursor) cursor); } /* *---------------------------------------------------------------------- * * XDefineCursor -- * * This function is called to update the cursor on a window. * Since the mouse might be in the specified window, we need to * check the specified window against the current mouse position * and grab state. * * Results: * None. * * Side effects: * May update the cursor. * *---------------------------------------------------------------------- */ void XDefineCursor(display, w, cursor) Display* display; Window w; Cursor cursor; { TkWindow *winPtr = (TkWindow *)Tk_IdToWindow(display, w); ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); if (tsdPtr->cursorWinPtr == winPtr) { UpdateCursor(winPtr); } display->request++; } /* *---------------------------------------------------------------------- * * TkGenerateActivateEvents -- * * This function is called by the Mac and Windows window manager * routines when a toplevel window is activated or deactivated. * Activate/Deactivate events will be sent to every subwindow of * the toplevel followed by a FocusIn/FocusOut message. * * Results: * None. * * Side effects: * Generates X events. * *---------------------------------------------------------------------- */ void TkGenerateActivateEvents(winPtr, active) TkWindow *winPtr; /* Toplevel to activate. */ int active; /* Non-zero if the window is being * activated, else 0.*/ { XEvent event; /* * Generate Activate and Deactivate events. This event * is sent to every subwindow in a toplevel window. */ event.xany.serial = winPtr->display->request++; event.xany.send_event = False; event.xany.display = winPtr->display; event.xany.window = winPtr->window; event.xany.type = active ? ActivateNotify : DeactivateNotify; TkQueueEventForAllChildren(winPtr, &event); }