/*1* tkFocus.c --2*3* This file contains procedures that manage the input4* focus for Tk.5*6* Copyright (c) 1990-1994 The Regents of the University of California.7* Copyright (c) 1994-1995 Sun Microsystems, Inc.8*9* See the file "license.terms" for information on usage and redistribution10* of this file, and for a DISCLAIMER OF ALL WARRANTIES.11*12* SCCS: @(#) tkFocus.c 1.27 96/02/15 18:53:2913*/1415#include "tkInt.h"1617/*18* For each top-level window that has ever received the focus, there19* is a record of the following type:20*/2122typedef struct TkFocusInfo {23TkWindow *topLevelPtr; /* Information about top-level window. */24TkWindow *focusWinPtr; /* The next time the focus comes to this25* top-level, it will be given to this26* window. */27struct TkFocusInfo *nextPtr;/* Next in list of all focus records for28* a given application. */29} FocusInfo;3031static int focusDebug = 0;3233/*34* The following magic value is stored in the "send_event" field of35* FocusIn and FocusOut events that are generated in this file. This36* allows us to separate "real" events coming from the server from37* those that we generated.38*/3940#define GENERATED_EVENT_MAGIC ((Bool) 0x547321ac)4142/*43* Forward declarations for procedures defined in this file:44*/454647static void ChangeXFocus _ANSI_ARGS_((TkWindow *topLevelPtr,48int focus));49static void FocusMapProc _ANSI_ARGS_((ClientData clientData,50XEvent *eventPtr));51static void GenerateFocusEvents _ANSI_ARGS_((TkWindow *sourcePtr,52TkWindow *destPtr));53static void SetFocus _ANSI_ARGS_((TkWindow *winPtr, int force));5455/*56*--------------------------------------------------------------57*58* Tk_FocusCmd --59*60* This procedure is invoked to process the "focus" Tcl command.61* See the user documentation for details on what it does.62*63* Results:64* A standard Tcl result.65*66* Side effects:67* See the user documentation.68*69*--------------------------------------------------------------70*/7172int73Tk_FocusCmd(clientData, interp, argc, argv)74ClientData clientData; /* Main window associated with75* interpreter. */76Tcl_Interp *interp; /* Current interpreter. */77int argc; /* Number of arguments. */78char **argv; /* Argument strings. */79{80Tk_Window tkwin = (Tk_Window) clientData;81TkWindow *winPtr = (TkWindow *) clientData;82TkWindow *newPtr, *focusWinPtr, *topLevelPtr;83FocusInfo *focusPtr;84char c;85size_t length;8687/*88* If invoked with no arguments, just return the current focus window.89*/9091if (argc == 1) {92focusWinPtr = TkGetFocus(winPtr);93if (focusWinPtr != NULL) {94interp->result = focusWinPtr->pathName;95}96return TCL_OK;97}9899/*100* If invoked with a single argument beginning with "." then focus101* on that window.102*/103104if (argc == 2) {105if (argv[1][0] == 0) {106return TCL_OK;107}108if (argv[1][0] == '.') {109newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[1], tkwin);110if (newPtr == NULL) {111return TCL_ERROR;112}113if (!(newPtr->flags & TK_ALREADY_DEAD)) {114SetFocus(newPtr, 0);115}116return TCL_OK;117}118}119120length = strlen(argv[1]);121c = argv[1][1];122if ((c == 'd') && (strncmp(argv[1], "-displayof", length) == 0)) {123if (argc != 3) {124Tcl_AppendResult(interp, "wrong # args: should be \"",125argv[0], " -displayof window\"", (char *) NULL);126return TCL_ERROR;127}128newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);129if (newPtr == NULL) {130return TCL_ERROR;131}132newPtr = TkGetFocus(newPtr);133if (newPtr != NULL) {134interp->result = newPtr->pathName;135}136} else if ((c == 'f') && (strncmp(argv[1], "-force", length) == 0)) {137if (argc != 3) {138Tcl_AppendResult(interp, "wrong # args: should be \"",139argv[0], " -force window\"", (char *) NULL);140return TCL_ERROR;141}142if (argv[2][0] == 0) {143return TCL_OK;144}145newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);146if (newPtr == NULL) {147return TCL_ERROR;148}149SetFocus(newPtr, 1);150} else if ((c == 'l') && (strncmp(argv[1], "-lastfor", length) == 0)) {151if (argc != 3) {152Tcl_AppendResult(interp, "wrong # args: should be \"",153argv[0], " -lastfor window\"", (char *) NULL);154return TCL_ERROR;155}156newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);157if (newPtr == NULL) {158return TCL_ERROR;159}160for (topLevelPtr = newPtr; topLevelPtr != NULL;161topLevelPtr = topLevelPtr->parentPtr) {162if (topLevelPtr->flags & TK_TOP_LEVEL) {163for (focusPtr = newPtr->mainPtr->focusPtr; focusPtr != NULL;164focusPtr = focusPtr->nextPtr) {165if (focusPtr->topLevelPtr == topLevelPtr) {166interp->result = focusPtr->focusWinPtr->pathName;167return TCL_OK;168}169}170interp->result = topLevelPtr->pathName;171return TCL_OK;172}173}174} else {175Tcl_AppendResult(interp, "bad option \"", argv[1],176"\": must be -displayof, -force, or -lastfor", (char *) NULL);177return TCL_ERROR;178}179return TCL_OK;180}181182/*183*--------------------------------------------------------------184*185* TkFocusFilterEvent --186*187* This procedure is invoked by Tk_HandleEvent when it encounters188* a FocusIn, FocusOut, Enter, or Leave event.189*190* Results:191* A return value of 1 means that Tk_HandleEvent should process192* the event normally (i.e. event handlers should be invoked).193* A return value of 0 means that this event should be ignored.194*195* Side effects:196* Additional events may be generated, and the focus may switch.197*198*--------------------------------------------------------------199*/200201int202TkFocusFilterEvent(winPtr, eventPtr)203TkWindow *winPtr; /* Window that focus event is directed to. */204XEvent *eventPtr; /* FocusIn or FocusOut event. */205{206/*207* Design notes: the window manager and X server work together to208* transfer the focus among top-level windows. This procedure takes209* care of transferring the focus from a top-level window to the210* actual window within that top-level that has the focus. We211* do this by synthesizing X events to move the focus around. None212* of the FocusIn and FocusOut events generated by X are ever used213* outside of this procedure; only the synthesized events get through214* to the rest of the application. At one point (e.g. Tk4.0b1) Tk215* used to call X to move the focus from a top-level to one of its216* descendants, then just pass through the events generated by X.217* This approach didn't work very well, for a variety of reasons.218* For example, if X generates the events they go at the back of219* the event queue, which could cause problems if other things220* have already happened, such as moving the focus to yet another221* window.222*/223224FocusInfo *focusPtr;225TkDisplay *dispPtr = winPtr->dispPtr;226TkWindow *newFocusPtr;227int retValue, delta;228229/*230* If this was a generated event, just turn off the generated231* flag and pass the event through.232*/233234if (eventPtr->xfocus.send_event == GENERATED_EVENT_MAGIC) {235eventPtr->xfocus.send_event = 0;236return 1;237}238239/*240* This was not a generated event. We'll return 1 (so that the241* event will be processed) if it's an Enter or Leave event, and242* 0 (so that the event won't be processed) if it's a FocusIn or243* FocusOut event. Also, skip NotifyPointer, NotifyPointerRoot,244* and NotifyInferior focus events immediately; they're not245* useful and tend to cause confusion.246*/247248if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {249retValue = 0;250if ((eventPtr->xfocus.detail == NotifyPointer)251|| (eventPtr->xfocus.detail == NotifyPointerRoot)252|| (eventPtr->xfocus.detail == NotifyInferior)) {253return retValue;254}255} else {256retValue = 1;257if (eventPtr->xcrossing.detail == NotifyInferior) {258return retValue;259}260}261262/*263* If winPtr isn't a top-level window than just ignore the event.264*/265266if (!(winPtr->flags & TK_TOP_LEVEL)) {267return retValue;268}269270/*271* If there is a grab in effect and this window is outside the272* grabbed tree, then ignore the event.273*/274275if (TkGrabState(winPtr) == TK_GRAB_EXCLUDED) {276return retValue;277}278279/*280* Find the FocusInfo structure for the window, and make a new one281* if there isn't one already.282*/283284for (focusPtr = winPtr->mainPtr->focusPtr; focusPtr != NULL;285focusPtr = focusPtr->nextPtr) {286if (focusPtr->topLevelPtr == winPtr) {287break;288}289}290if (focusPtr == NULL) {291focusPtr = (FocusInfo *) ckalloc(sizeof(FocusInfo));292focusPtr->topLevelPtr = focusPtr->focusWinPtr = winPtr;293focusPtr->nextPtr = winPtr->mainPtr->focusPtr;294winPtr->mainPtr->focusPtr = focusPtr;295}296297/*298* It is possible that there were outstanding FocusIn and FocusOut299* events on their way to us at the time the focus was changed300* internally with the "focus" command. If so, these events could301* potentially cause us to lose the focus (switch it to the window302* of the last FocusIn event) even though the focus change occurred303* after those events. The following code detects this and puts304* the focus back to the place where it was rightfully set.305*/306307newFocusPtr = focusPtr->focusWinPtr;308delta = eventPtr->xfocus.serial - winPtr->mainPtr->focusSerial;309if (focusDebug) {310printf("check event serial %d, delta %d\n",311(int) eventPtr->xfocus.serial, delta);312}313if ((delta < 0) && (winPtr->mainPtr->lastFocusPtr != NULL)) {314newFocusPtr = winPtr->mainPtr->lastFocusPtr;315if (focusDebug) {316printf("reverting to %s instead of %s\n", newFocusPtr->pathName,317focusPtr->focusWinPtr->pathName);318}319}320321if (eventPtr->type == FocusIn) {322GenerateFocusEvents(dispPtr->focusWinPtr, newFocusPtr);323dispPtr->focusWinPtr = newFocusPtr;324dispPtr->implicitWinPtr = NULL;325if (focusDebug) {326printf("Focussed on %s\n", newFocusPtr->pathName);327}328} else if (eventPtr->type == FocusOut) {329GenerateFocusEvents(dispPtr->focusWinPtr, (TkWindow *) NULL);330dispPtr->focusWinPtr = NULL;331dispPtr->implicitWinPtr = NULL;332if (focusDebug) {333printf("Unfocussed from %s, detail %d\n", winPtr->pathName,334eventPtr->xfocus.detail);335}336} else if (eventPtr->type == EnterNotify) {337/*338* If there is no window manager, or if the window manager isn't339* moving the focus around (e.g. the disgusting "NoTitleFocus"340* option has been selected in twm), then we won't get FocusIn341* or FocusOut events. Instead, the "focus" field will be set342* in an Enter event to indicate that we've already got the focus343* when then mouse enters the window (even though we didn't get344* a FocusIn event). Watch for this and grab the focus when it345* happens.346*/347348if (eventPtr->xcrossing.focus && (dispPtr->focusWinPtr == NULL)) {349GenerateFocusEvents(dispPtr->focusWinPtr, newFocusPtr);350dispPtr->focusWinPtr = newFocusPtr;351dispPtr->implicitWinPtr = winPtr;352if (focusDebug) {353printf("Focussed implicitly on %s\n",354newFocusPtr->pathName);355}356}357} else if (eventPtr->type == LeaveNotify) {358/*359* If the pointer just left a window for which we automatically360* claimed the focus on enter, generate FocusOut events. Note:361* dispPtr->implicitWinPtr may not be the same as362* dispPtr->focusWinPtr (e.g. because the "focus" command was363* used to redirect the focus after it arrived at364* dispPtr->implicitWinPtr)!!365*/366367if (dispPtr->implicitWinPtr == winPtr) {368GenerateFocusEvents(dispPtr->focusWinPtr, (TkWindow *) NULL);369dispPtr->focusWinPtr = NULL;370dispPtr->implicitWinPtr = NULL;371if (focusDebug) {372printf("Defocussed implicitly\n");373}374}375}376return retValue;377}378379/*380*----------------------------------------------------------------------381*382* SetFocus --383*384* This procedure is invoked to change the focus window for a385* given display in a given application.386*387* Results:388* None.389*390* Side effects:391* Event handlers may be invoked to process the change of392* focus.393*394*----------------------------------------------------------------------395*/396397static void398SetFocus(winPtr, force)399TkWindow *winPtr; /* Window that is to be the new focus for400* its display and application. */401int force; /* If non-zero, set the X focus to this402* window even if the application doesn't403* currently have the X focus. */404{405TkDisplay *dispPtr = winPtr->dispPtr;406FocusInfo *focusPtr;407TkWindow *topLevelPtr, *topLevelPtr2;408409if (winPtr == dispPtr->focusWinPtr) {410return;411}412413/*414* Find the top-level window for winPtr, then find (or create)415* a record for the top-level.416*/417418for (topLevelPtr = winPtr; ; topLevelPtr = topLevelPtr->parentPtr) {419if (topLevelPtr == NULL) {420/*421* The window is being deleted. No point in worrying about422* giving it the focus.423*/424425return;426}427if (topLevelPtr->flags & TK_TOP_LEVEL) {428break;429}430}431for (focusPtr = winPtr->mainPtr->focusPtr; focusPtr != NULL;432focusPtr = focusPtr->nextPtr) {433if (focusPtr->topLevelPtr == topLevelPtr) {434break;435}436}437if (focusPtr == NULL) {438focusPtr = (FocusInfo *) ckalloc(sizeof(FocusInfo));439focusPtr->topLevelPtr = topLevelPtr;440focusPtr->nextPtr = winPtr->mainPtr->focusPtr;441winPtr->mainPtr->focusPtr = focusPtr;442}443444/*445* Reset the focus, but only if the application already has the446* input focus or "force" has been specified.447*/448449focusPtr->focusWinPtr = winPtr;450Tk_MakeWindowExist((Tk_Window) winPtr);451if (force || ((dispPtr->focusWinPtr != NULL)452&& (dispPtr->focusWinPtr->mainPtr == winPtr->mainPtr))) {453/*454* Reset the focus in X if it has changed top-levels and if the455* new top-level isn't override-redirect (the only reason to456* change the X focus is so that the window manager can redecorate457* the focus window, but if it's override-redirect then it won't458* be decorated anyway; also, changing the focus to menus causes459* all sorts of problems with olvwm: the focus gets lost if460* keyboard traversal is used to move among menus.461*/462463if (dispPtr->focusWinPtr != NULL) {464for (topLevelPtr2 = dispPtr->focusWinPtr;465(topLevelPtr2 != NULL)466&& !(topLevelPtr2->flags & TK_TOP_LEVEL);467topLevelPtr2 = topLevelPtr2->parentPtr) {468/* Empty loop body. */469}470} else {471topLevelPtr2 = NULL;472}473if ((topLevelPtr2 != topLevelPtr)474&& !(topLevelPtr->atts.override_redirect)) {475if (dispPtr->focusOnMapPtr != NULL) {476Tk_DeleteEventHandler((Tk_Window) dispPtr->focusOnMapPtr,477StructureNotifyMask, FocusMapProc,478(ClientData) dispPtr->focusOnMapPtr);479dispPtr->focusOnMapPtr = NULL;480}481if (topLevelPtr->flags & TK_MAPPED) {482ChangeXFocus(topLevelPtr, force);483} else {484/*485* The window isn't mapped, so we can't give it the focus486* right now. Create an event handler that will give it487* the focus as soon as it is mapped.488*/489490Tk_CreateEventHandler((Tk_Window) topLevelPtr,491StructureNotifyMask, FocusMapProc,492(ClientData) topLevelPtr);493dispPtr->focusOnMapPtr = topLevelPtr;494dispPtr->forceFocus = force;495}496}497GenerateFocusEvents(dispPtr->focusWinPtr, winPtr);498dispPtr->focusWinPtr = winPtr;499}500501/*502* Remember the current serial number for the X server and issue503* a dummy server request. This marks the position at which we504* changed the focus, so we can distinguish FocusIn and FocusOut505* events on either side of the mark.506*/507508winPtr->mainPtr->lastFocusPtr = winPtr;509winPtr->mainPtr->focusSerial = NextRequest(winPtr->display);510XNoOp(winPtr->display);511if (focusDebug) {512printf("focus marking for %s at %d\n", winPtr->pathName,513(int) winPtr->mainPtr->focusSerial);514}515}516517/*518*----------------------------------------------------------------------519*520* TkGetFocus --521*522* Given a window, this procedure returns the current focus523* window for its application and display.524*525* Results:526* The return value is a pointer to the window that currently527* has the input focus for the specified application and528* display, or NULL if none.529*530* Side effects:531* None.532*533*----------------------------------------------------------------------534*/535536TkWindow *537TkGetFocus(winPtr)538TkWindow *winPtr; /* Window that selects an application539* and a display. */540{541TkWindow *focusWinPtr;542543focusWinPtr = winPtr->dispPtr->focusWinPtr;544if ((focusWinPtr != NULL) && (focusWinPtr->mainPtr == winPtr->mainPtr)) {545return focusWinPtr;546}547return (TkWindow *) NULL;548}549550/*551*----------------------------------------------------------------------552*553* TkFocusDeadWindow --554*555* This procedure is invoked when it is determined that556* a window is dead. It cleans up focus-related information557* about the window.558*559* Results:560* None.561*562* Side effects:563* Various things get cleaned up and recycled.564*565*----------------------------------------------------------------------566*/567568void569TkFocusDeadWindow(winPtr)570register TkWindow *winPtr; /* Information about the window571* that is being deleted. */572{573FocusInfo *focusPtr, *prevPtr;574TkDisplay *dispPtr = winPtr->dispPtr;575576/*577* Search for focus records that refer to this window either as578* the top-level window or the current focus window.579*/580581for (prevPtr = NULL, focusPtr = winPtr->mainPtr->focusPtr;582focusPtr != NULL;583prevPtr = focusPtr, focusPtr = focusPtr->nextPtr) {584if (winPtr == focusPtr->topLevelPtr) {585/*586* The top-level window is the one being deleted: free587* the focus record and release the focus back to PointerRoot588* if we acquired it implicitly.589*/590591if (dispPtr->implicitWinPtr == winPtr) {592if (focusDebug) {593printf("releasing focus to root after %s died\n",594focusPtr->topLevelPtr->pathName);595}596dispPtr->implicitWinPtr = NULL;597dispPtr->focusWinPtr = NULL;598}599if (dispPtr->focusWinPtr == focusPtr->focusWinPtr) {600dispPtr->focusWinPtr = NULL;601}602if (dispPtr->focusOnMapPtr == focusPtr->topLevelPtr) {603dispPtr->focusOnMapPtr = NULL;604}605if (prevPtr == NULL) {606winPtr->mainPtr->focusPtr = focusPtr->nextPtr;607} else {608prevPtr->nextPtr = focusPtr->nextPtr;609}610ckfree((char *) focusPtr);611break;612} else if (winPtr == focusPtr->focusWinPtr) {613/*614* The deleted window had the focus for its top-level:615* move the focus to the top-level itself.616*/617618focusPtr->focusWinPtr = focusPtr->topLevelPtr;619if ((dispPtr->focusWinPtr == winPtr)620&& !(focusPtr->topLevelPtr->flags & TK_ALREADY_DEAD)) {621if (focusDebug) {622printf("forwarding focus to %s after %s died\n",623focusPtr->topLevelPtr->pathName, winPtr->pathName);624}625GenerateFocusEvents(dispPtr->focusWinPtr,626focusPtr->topLevelPtr);627dispPtr->focusWinPtr = focusPtr->topLevelPtr;628}629break;630}631}632633if (winPtr->mainPtr->lastFocusPtr == winPtr) {634winPtr->mainPtr->lastFocusPtr = NULL;635}636}637638/*639*----------------------------------------------------------------------640*641* GenerateFocusEvents --642*643* This procedure is called to create FocusIn and FocusOut events to644* move the input focus from one window to another.645*646* Results:647* None.648*649* Side effects:650* FocusIn and FocusOut events are generated.651*652*----------------------------------------------------------------------653*/654655static void656GenerateFocusEvents(sourcePtr, destPtr)657TkWindow *sourcePtr; /* Window that used to have the focus (may658* be NULL). */659TkWindow *destPtr; /* New window to have the focus (may be660* NULL). */661662{663XEvent event;664TkWindow *winPtr;665666winPtr = sourcePtr;667if (winPtr == NULL) {668winPtr = destPtr;669if (winPtr == NULL) {670return;671}672}673674event.xfocus.serial = LastKnownRequestProcessed(winPtr->display);675event.xfocus.send_event = GENERATED_EVENT_MAGIC;676event.xfocus.display = winPtr->display;677event.xfocus.mode = NotifyNormal;678TkInOutEvents(&event, sourcePtr, destPtr, FocusOut, FocusIn,679TCL_QUEUE_MARK);680}681682/*683*----------------------------------------------------------------------684*685* ChangeXFocus --686*687* This procedure is invoked to move the official X focus from688* one top-level to another. We do this when the application689* changes the focus window from one top-level to another, in690* order to notify the window manager so that it can highlight691* the new focus top-level.692*693* Results:694* None.695*696* Side effects:697* The official X focus window changes; the application's focus698* window isn't changed by this procedure.699*700*----------------------------------------------------------------------701*/702703static void704ChangeXFocus(topLevelPtr, force)705TkWindow *topLevelPtr; /* Top-level window that is to receive706* the X focus. */707int force; /* Non-zero means claim the focus even708* if it didn't originally belong to709* topLevelPtr's application. */710{711TkDisplay *dispPtr = topLevelPtr->dispPtr;712TkWindow *winPtr;713Window focusWindow;714int dummy;715Tk_ErrorHandler errHandler;716717/*718* If the focus was received implicitly, then there's no advantage719* in setting an explicit focus; just return.720*/721722if (dispPtr->implicitWinPtr != NULL) {723return;724}725726/*727* Check to make sure that the focus is still in one of the728* windows of this application. Furthermore, grab the server729* to make sure that the focus doesn't change in the middle730* of this operation.731*/732733if (!focusDebug) {734XGrabServer(dispPtr->display);735}736if (!force) {737XGetInputFocus(dispPtr->display, &focusWindow, &dummy);738winPtr = (TkWindow *) Tk_IdToWindow(dispPtr->display, focusWindow);739if ((winPtr == NULL) || (winPtr->mainPtr != topLevelPtr->mainPtr)) {740goto done;741}742}743744/*745* Tell X to change the focus. Ignore errors that occur when changing746* the focus: it is still possible that the window we're focussing747* to could have gotten unmapped, which will generate an error.748*/749750errHandler = Tk_CreateErrorHandler(dispPtr->display, -1, -1, -1,751(Tk_ErrorProc *) NULL, (ClientData) NULL);752XSetInputFocus(dispPtr->display, topLevelPtr->window, RevertToParent,753CurrentTime);754Tk_DeleteErrorHandler(errHandler);755if (focusDebug) {756printf("Set X focus to %s\n", topLevelPtr->pathName);757}758759done:760if (!focusDebug) {761XUngrabServer(dispPtr->display);762}763}764765/*766*----------------------------------------------------------------------767*768* FocusMapProc --769*770* This procedure is called as an event handler for StructureNotify771* events, if a window receives the focus at a time when its772* toplevel isn't mapped. The procedure is needed because X773* won't allow the focus to be set to an unmapped window; we774* detect when the toplevel is mapped and set the focus to it then.775*776* Results:777* None.778*779* Side effects:780* If this is a map event, the focus gets set to the toplevel781* given by clientData.782*783*----------------------------------------------------------------------784*/785786static void787FocusMapProc(clientData, eventPtr)788ClientData clientData; /* Toplevel window. */789XEvent *eventPtr; /* Information about event. */790{791TkWindow *winPtr = (TkWindow *) clientData;792TkDisplay *dispPtr = winPtr->dispPtr;793794if (eventPtr->type == MapNotify) {795ChangeXFocus(winPtr, dispPtr->forceFocus);796Tk_DeleteEventHandler((Tk_Window) winPtr, StructureNotifyMask,797FocusMapProc, clientData);798dispPtr->focusOnMapPtr = NULL;799}800}801802803