1 /*
2  * tkFocus.c --
3  *
4  *	This file contains procedures that manage the input
5  *	focus for Tk.
6  *
7  * Copyright (c) 1990-1994 The Regents of the University of California.
8  * Copyright (c) 1994-1995 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: @(#) tkFocus.c 1.27 96/02/15 18:53:29
14  */
15 
16 #include "tkInt.h"
17 
18 /*
19  * For each top-level window that has ever received the focus, there
20  * is a record of the following type:
21  */
22 
23 typedef struct TkFocusInfo {
24     TkWindow *topLevelPtr;	/* Information about top-level window. */
25     TkWindow *focusWinPtr;	/* The next time the focus comes to this
26 				 * top-level, it will be given to this
27 				 * window. */
28     struct TkFocusInfo *nextPtr;/* Next in list of all focus records for
29 				 * a given application. */
30 } FocusInfo;
31 
32 static int focusDebug = 0;
33 
34 /*
35  * The following magic value is stored in the "send_event" field of
36  * FocusIn and FocusOut events that are generated in this file.  This
37  * allows us to separate "real" events coming from the server from
38  * those that we generated.
39  */
40 
41 #define GENERATED_EVENT_MAGIC ((Bool) 0x547321ac)
42 
43 /*
44  * Forward declarations for procedures defined in this file:
45  */
46 
47 
48 static void		ChangeXFocus _ANSI_ARGS_((TkWindow *topLevelPtr,
49 			    int focus));
50 static void		FocusMapProc _ANSI_ARGS_((ClientData clientData,
51 			    XEvent *eventPtr));
52 static void		GenerateFocusEvents _ANSI_ARGS_((TkWindow *sourcePtr,
53 			    TkWindow *destPtr));
54 static void		SetFocus _ANSI_ARGS_((TkWindow *winPtr, int force));
55 
56 /*
57  *--------------------------------------------------------------
58  *
59  * Tk_FocusCmd --
60  *
61  *	This procedure is invoked to process the "focus" Tcl command.
62  *	See the user documentation for details on what it does.
63  *
64  * Results:
65  *	A standard Tcl result.
66  *
67  * Side effects:
68  *	See the user documentation.
69  *
70  *--------------------------------------------------------------
71  */
72 
73 int
Tk_FocusCmd(clientData,interp,argc,argv)74 Tk_FocusCmd(clientData, interp, argc, argv)
75     ClientData clientData;	/* Main window associated with
76 				 * interpreter. */
77     Tcl_Interp *interp;		/* Current interpreter. */
78     int argc;			/* Number of arguments. */
79     char **argv;		/* Argument strings. */
80 {
81     Tk_Window tkwin = (Tk_Window) clientData;
82     TkWindow *winPtr = (TkWindow *) clientData;
83     TkWindow *newPtr, *focusWinPtr, *topLevelPtr;
84     FocusInfo *focusPtr;
85     char c;
86     size_t length;
87 
88     /*
89      * If invoked with no arguments, just return the current focus window.
90      */
91 
92     if (argc == 1) {
93 	focusWinPtr = TkGetFocus(winPtr);
94 	if (focusWinPtr != NULL) {
95 	    interp->result = focusWinPtr->pathName;
96 	}
97 	return TCL_OK;
98     }
99 
100     /*
101      * If invoked with a single argument beginning with "." then focus
102      * on that window.
103      */
104 
105     if (argc == 2) {
106 	if (argv[1][0] == 0) {
107 	    return TCL_OK;
108 	}
109 	if (argv[1][0] == '.') {
110 	    newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[1], tkwin);
111 	    if (newPtr == NULL) {
112 		return TCL_ERROR;
113 	    }
114 	    if (!(newPtr->flags & TK_ALREADY_DEAD)) {
115 		SetFocus(newPtr, 0);
116 	    }
117 	    return TCL_OK;
118 	}
119     }
120 
121     length = strlen(argv[1]);
122     c = argv[1][1];
123     if ((c == 'd') && (strncmp(argv[1], "-displayof", length) == 0)) {
124 	if (argc != 3) {
125 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
126 		    argv[0], " -displayof window\"", (char *) NULL);
127 	    return TCL_ERROR;
128 	}
129 	newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);
130 	if (newPtr == NULL) {
131 	    return TCL_ERROR;
132 	}
133 	newPtr = TkGetFocus(newPtr);
134 	if (newPtr != NULL) {
135 	    interp->result = newPtr->pathName;
136 	}
137     } else if ((c == 'f') && (strncmp(argv[1], "-force", length) == 0)) {
138 	if (argc != 3) {
139 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
140 		    argv[0], " -force window\"", (char *) NULL);
141 	    return TCL_ERROR;
142 	}
143 	if (argv[2][0] == 0) {
144 	    return TCL_OK;
145 	}
146 	newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);
147 	if (newPtr == NULL) {
148 	    return TCL_ERROR;
149 	}
150 	SetFocus(newPtr, 1);
151     } else if ((c == 'l') && (strncmp(argv[1], "-lastfor", length) == 0)) {
152 	if (argc != 3) {
153 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
154 		    argv[0], " -lastfor window\"", (char *) NULL);
155 	    return TCL_ERROR;
156 	}
157 	newPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2], tkwin);
158 	if (newPtr == NULL) {
159 	    return TCL_ERROR;
160 	}
161 	for (topLevelPtr = newPtr; topLevelPtr != NULL;
162 		topLevelPtr = topLevelPtr->parentPtr)  {
163 	    if (topLevelPtr->flags & TK_TOP_LEVEL) {
164 		for (focusPtr = newPtr->mainPtr->focusPtr; focusPtr != NULL;
165 			focusPtr = focusPtr->nextPtr) {
166 		    if (focusPtr->topLevelPtr == topLevelPtr) {
167 			interp->result = focusPtr->focusWinPtr->pathName;
168 			return TCL_OK;
169 		    }
170 		}
171 		interp->result = topLevelPtr->pathName;
172 		return TCL_OK;
173 	    }
174 	}
175     } else {
176 	Tcl_AppendResult(interp, "bad option \"", argv[1],
177 		"\": must be -displayof, -force, or -lastfor", (char *) NULL);
178 	return TCL_ERROR;
179     }
180     return TCL_OK;
181 }
182 
183 /*
184  *--------------------------------------------------------------
185  *
186  * TkFocusFilterEvent --
187  *
188  *	This procedure is invoked by Tk_HandleEvent when it encounters
189  *	a FocusIn, FocusOut, Enter, or Leave event.
190  *
191  * Results:
192  *	A return value of 1 means that Tk_HandleEvent should process
193  *	the event normally (i.e. event handlers should be invoked).
194  *	A return value of 0 means that this event should be ignored.
195  *
196  * Side effects:
197  *	Additional events may be generated, and the focus may switch.
198  *
199  *--------------------------------------------------------------
200  */
201 
202 int
TkFocusFilterEvent(winPtr,eventPtr)203 TkFocusFilterEvent(winPtr, eventPtr)
204     TkWindow *winPtr;		/* Window that focus event is directed to. */
205     XEvent *eventPtr;		/* FocusIn or FocusOut event. */
206 {
207     /*
208      * Design notes: the window manager and X server work together to
209      * transfer the focus among top-level windows.  This procedure takes
210      * care of transferring the focus from a top-level window to the
211      * actual window within that top-level that has the focus.  We
212      * do this by synthesizing X events to move the focus around.  None
213      * of the FocusIn and FocusOut events generated by X are ever used
214      * outside of this procedure;  only the synthesized events get through
215      * to the rest of the application.  At one point (e.g. Tk4.0b1) Tk
216      * used to call X to move the focus from a top-level to one of its
217      * descendants, then just pass through the events generated by X.
218      * This approach didn't work very well, for a variety of reasons.
219      * For example, if X generates the events they go at the back of
220      * the event queue, which could cause problems if other things
221      * have already happened, such as moving the focus to yet another
222      * window.
223      */
224 
225     FocusInfo *focusPtr;
226     TkDisplay *dispPtr = winPtr->dispPtr;
227     TkWindow *newFocusPtr;
228     int retValue, delta;
229 
230     /*
231      * If this was a generated event, just turn off the generated
232      * flag and pass the event through.
233      */
234 
235     if (eventPtr->xfocus.send_event == GENERATED_EVENT_MAGIC) {
236 	eventPtr->xfocus.send_event = 0;
237 	return 1;
238     }
239 
240     /*
241      * This was not a generated event.  We'll return 1 (so that the
242      * event will be processed) if it's an Enter or Leave event, and
243      * 0 (so that the event won't be processed) if it's a FocusIn or
244      * FocusOut event.  Also, skip NotifyPointer, NotifyPointerRoot,
245      * and NotifyInferior focus events immediately; they're not
246      * useful and tend to cause confusion.
247      */
248 
249     if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
250 	retValue = 0;
251 	if ((eventPtr->xfocus.detail == NotifyPointer)
252 		|| (eventPtr->xfocus.detail == NotifyPointerRoot)
253 		|| (eventPtr->xfocus.detail == NotifyInferior)) {
254 	    return retValue;
255 	}
256     } else {
257 	retValue = 1;
258 	if (eventPtr->xcrossing.detail == NotifyInferior) {
259 	    return retValue;
260 	}
261     }
262 
263     /*
264      * If winPtr isn't a top-level window than just ignore the event.
265      */
266 
267     if (!(winPtr->flags & TK_TOP_LEVEL)) {
268 	return retValue;
269     }
270 
271     /*
272      * If there is a grab in effect and this window is outside the
273      * grabbed tree, then ignore the event.
274      */
275 
276     if (TkGrabState(winPtr) == TK_GRAB_EXCLUDED)  {
277 	return retValue;
278     }
279 
280     /*
281      * Find the FocusInfo structure for the window, and make a new one
282      * if there isn't one already.
283      */
284 
285     for (focusPtr = winPtr->mainPtr->focusPtr; focusPtr != NULL;
286 	    focusPtr = focusPtr->nextPtr) {
287 	if (focusPtr->topLevelPtr == winPtr) {
288 	    break;
289 	}
290     }
291     if (focusPtr == NULL) {
292 	focusPtr = (FocusInfo *) ckalloc(sizeof(FocusInfo));
293 	focusPtr->topLevelPtr = focusPtr->focusWinPtr = winPtr;
294 	focusPtr->nextPtr = winPtr->mainPtr->focusPtr;
295 	winPtr->mainPtr->focusPtr = focusPtr;
296     }
297 
298     /*
299      * It is possible that there were outstanding FocusIn and FocusOut
300      * events on their way to us at the time the focus was changed
301      * internally with the "focus" command.  If so, these events could
302      * potentially cause us to lose the focus (switch it to the window
303      * of the last FocusIn event) even though the focus change occurred
304      * after those events.  The following code detects this and puts
305      * the focus back to the place where it was rightfully set.
306      */
307 
308     newFocusPtr = focusPtr->focusWinPtr;
309     delta = eventPtr->xfocus.serial - winPtr->mainPtr->focusSerial;
310     if (focusDebug) {
311 	printf("check event serial %d, delta %d\n",
312 		(int) eventPtr->xfocus.serial, delta);
313     }
314     if ((delta < 0) && (winPtr->mainPtr->lastFocusPtr != NULL)) {
315 	newFocusPtr = winPtr->mainPtr->lastFocusPtr;
316 	if (focusDebug) {
317 	    printf("reverting to %s instead of %s\n", newFocusPtr->pathName,
318 		    focusPtr->focusWinPtr->pathName);
319 	}
320     }
321 
322     if (eventPtr->type == FocusIn) {
323 	GenerateFocusEvents(dispPtr->focusWinPtr, newFocusPtr);
324 	dispPtr->focusWinPtr = newFocusPtr;
325 	dispPtr->implicitWinPtr = NULL;
326 	if (focusDebug) {
327 	    printf("Focussed on %s\n", newFocusPtr->pathName);
328 	}
329     } else if (eventPtr->type == FocusOut) {
330 	GenerateFocusEvents(dispPtr->focusWinPtr, (TkWindow *) NULL);
331 	dispPtr->focusWinPtr = NULL;
332 	dispPtr->implicitWinPtr = NULL;
333 	if (focusDebug) {
334 	    printf("Unfocussed from %s, detail %d\n", winPtr->pathName,
335 		    eventPtr->xfocus.detail);
336 	}
337     } else if (eventPtr->type == EnterNotify) {
338 	/*
339 	 * If there is no window manager, or if the window manager isn't
340 	 * moving the focus around (e.g. the disgusting "NoTitleFocus"
341 	 * option has been selected in twm), then we won't get FocusIn
342 	 * or FocusOut events.  Instead, the "focus" field will be set
343 	 * in an Enter event to indicate that we've already got the focus
344 	 * when then mouse enters the window (even though we didn't get
345 	 * a FocusIn event).  Watch for this and grab the focus when it
346 	 * happens.
347 	 */
348 
349 	if (eventPtr->xcrossing.focus && (dispPtr->focusWinPtr == NULL)) {
350 	    GenerateFocusEvents(dispPtr->focusWinPtr, newFocusPtr);
351 	    dispPtr->focusWinPtr = newFocusPtr;
352 	    dispPtr->implicitWinPtr = winPtr;
353 	    if (focusDebug) {
354 		printf("Focussed implicitly on %s\n",
355 			newFocusPtr->pathName);
356 	    }
357 	}
358     } else if (eventPtr->type == LeaveNotify) {
359 	/*
360 	 * If the pointer just left a window for which we automatically
361 	 * claimed the focus on enter, generate FocusOut events.  Note:
362 	 * dispPtr->implicitWinPtr may not be the same as
363 	 * dispPtr->focusWinPtr (e.g. because the "focus" command was
364 	 * used to redirect the focus after it arrived at
365 	 * dispPtr->implicitWinPtr)!!
366 	 */
367 
368 	if (dispPtr->implicitWinPtr == winPtr) {
369 	    GenerateFocusEvents(dispPtr->focusWinPtr, (TkWindow *) NULL);
370 	    dispPtr->focusWinPtr = NULL;
371 	    dispPtr->implicitWinPtr = NULL;
372 	    if (focusDebug) {
373 		printf("Defocussed implicitly\n");
374 	    }
375 	}
376     }
377     return retValue;
378 }
379 
380 /*
381  *----------------------------------------------------------------------
382  *
383  * SetFocus --
384  *
385  *	This procedure is invoked to change the focus window for a
386  *	given display in a given application.
387  *
388  * Results:
389  *	None.
390  *
391  * Side effects:
392  *	Event handlers may be invoked to process the change of
393  *	focus.
394  *
395  *----------------------------------------------------------------------
396  */
397 
398 static void
SetFocus(winPtr,force)399 SetFocus(winPtr, force)
400     TkWindow *winPtr;		/* Window that is to be the new focus for
401 				 * its display and application. */
402     int force;			/* If non-zero, set the X focus to this
403 				 * window even if the application doesn't
404 				 * currently have the X focus. */
405 {
406     TkDisplay *dispPtr = winPtr->dispPtr;
407     FocusInfo *focusPtr;
408     TkWindow *topLevelPtr, *topLevelPtr2;
409 
410     if (winPtr == dispPtr->focusWinPtr) {
411 	return;
412     }
413 
414     /*
415      * Find the top-level window for winPtr, then find (or create)
416      * a record for the top-level.
417      */
418 
419     for (topLevelPtr = winPtr; ; topLevelPtr = topLevelPtr->parentPtr)  {
420 	if (topLevelPtr == NULL) {
421 	    /*
422 	     * The window is being deleted.  No point in worrying about
423 	     * giving it the focus.
424 	     */
425 
426 	    return;
427 	}
428 	if (topLevelPtr->flags & TK_TOP_LEVEL) {
429 	    break;
430 	}
431     }
432     for (focusPtr = winPtr->mainPtr->focusPtr; focusPtr != NULL;
433 	    focusPtr = focusPtr->nextPtr) {
434 	if (focusPtr->topLevelPtr == topLevelPtr) {
435 	    break;
436 	}
437     }
438     if (focusPtr == NULL) {
439 	focusPtr = (FocusInfo *) ckalloc(sizeof(FocusInfo));
440 	focusPtr->topLevelPtr = topLevelPtr;
441 	focusPtr->nextPtr = winPtr->mainPtr->focusPtr;
442 	winPtr->mainPtr->focusPtr = focusPtr;
443     }
444 
445     /*
446      * Reset the focus, but only if the application already has the
447      * input focus or "force" has been specified.
448      */
449 
450     focusPtr->focusWinPtr = winPtr;
451     Tk_MakeWindowExist((Tk_Window) winPtr);
452     if (force || ((dispPtr->focusWinPtr != NULL)
453 	    && (dispPtr->focusWinPtr->mainPtr == winPtr->mainPtr))) {
454 	/*
455 	 * Reset the focus in X if it has changed top-levels and if the
456 	 * new top-level isn't override-redirect (the only reason to
457 	 * change the X focus is so that the window manager can redecorate
458 	 * the focus window, but if it's override-redirect then it won't
459 	 * be decorated anyway;  also, changing the focus to menus causes
460 	 * all sorts of problems with olvwm:  the focus gets lost if
461 	 * keyboard traversal is used to move among menus.
462 	 */
463 
464 	if (dispPtr->focusWinPtr != NULL) {
465 	    for (topLevelPtr2 = dispPtr->focusWinPtr;
466 		    (topLevelPtr2 != NULL)
467 			&& !(topLevelPtr2->flags & TK_TOP_LEVEL);
468 		    topLevelPtr2 = topLevelPtr2->parentPtr)  {
469 		/* Empty loop body. */
470 	    }
471 	} else {
472 	    topLevelPtr2 = NULL;
473 	}
474 	if ((topLevelPtr2 != topLevelPtr)
475 		&& !(topLevelPtr->atts.override_redirect)) {
476 	    if (dispPtr->focusOnMapPtr != NULL) {
477 		Tk_DeleteEventHandler((Tk_Window) dispPtr->focusOnMapPtr,
478 			StructureNotifyMask, FocusMapProc,
479 			(ClientData) dispPtr->focusOnMapPtr);
480 		dispPtr->focusOnMapPtr = NULL;
481 	    }
482 	    if (topLevelPtr->flags & TK_MAPPED) {
483 		ChangeXFocus(topLevelPtr, force);
484 	    } else {
485 		/*
486 		 * The window isn't mapped, so we can't give it the focus
487 		 * right now.  Create an event handler that will give it
488 		 * the focus as soon as it is mapped.
489 		 */
490 
491 		Tk_CreateEventHandler((Tk_Window) topLevelPtr,
492 			StructureNotifyMask, FocusMapProc,
493 			(ClientData) topLevelPtr);
494 		dispPtr->focusOnMapPtr = topLevelPtr;
495 		dispPtr->forceFocus = force;
496 	    }
497 	}
498 	GenerateFocusEvents(dispPtr->focusWinPtr, winPtr);
499 	dispPtr->focusWinPtr = winPtr;
500     }
501 
502     /*
503      * Remember the current serial number for the X server and issue
504      * a dummy server request.  This marks the position at which we
505      * changed the focus, so we can distinguish FocusIn and FocusOut
506      * events on either side of the mark.
507      */
508 
509     winPtr->mainPtr->lastFocusPtr = winPtr;
510     winPtr->mainPtr->focusSerial = NextRequest(winPtr->display);
511     XNoOp(winPtr->display);
512     if (focusDebug) {
513 	printf("focus marking for %s at %d\n", winPtr->pathName,
514 		(int) winPtr->mainPtr->focusSerial);
515     }
516 }
517 
518 /*
519  *----------------------------------------------------------------------
520  *
521  * TkGetFocus --
522  *
523  *	Given a window, this procedure returns the current focus
524  *	window for its application and display.
525  *
526  * Results:
527  *	The return value is a pointer to the window that currently
528  *	has the input focus for the specified application and
529  *	display, or NULL if none.
530  *
531  * Side effects:
532  *	None.
533  *
534  *----------------------------------------------------------------------
535  */
536 
537 TkWindow *
TkGetFocus(winPtr)538 TkGetFocus(winPtr)
539     TkWindow *winPtr;		/* Window that selects an application
540 				 * and a display. */
541 {
542     TkWindow *focusWinPtr;
543 
544     focusWinPtr = winPtr->dispPtr->focusWinPtr;
545     if ((focusWinPtr != NULL) && (focusWinPtr->mainPtr == winPtr->mainPtr)) {
546 	return focusWinPtr;
547     }
548     return (TkWindow *) NULL;
549 }
550 
551 /*
552  *----------------------------------------------------------------------
553  *
554  * TkFocusDeadWindow --
555  *
556  *	This procedure is invoked when it is determined that
557  *	a window is dead.  It cleans up focus-related information
558  *	about the window.
559  *
560  * Results:
561  *	None.
562  *
563  * Side effects:
564  *	Various things get cleaned up and recycled.
565  *
566  *----------------------------------------------------------------------
567  */
568 
569 void
TkFocusDeadWindow(winPtr)570 TkFocusDeadWindow(winPtr)
571     register TkWindow *winPtr;		/* Information about the window
572 					 * that is being deleted. */
573 {
574     FocusInfo *focusPtr, *prevPtr;
575     TkDisplay *dispPtr = winPtr->dispPtr;
576 
577     /*
578      * Search for focus records that refer to this window either as
579      * the top-level window or the current focus window.
580      */
581 
582     for (prevPtr = NULL, focusPtr = winPtr->mainPtr->focusPtr;
583 	    focusPtr != NULL;
584 	    prevPtr = focusPtr, focusPtr = focusPtr->nextPtr) {
585 	if (winPtr == focusPtr->topLevelPtr) {
586 	    /*
587 	     * The top-level window is the one being deleted: free
588 	     * the focus record and release the focus back to PointerRoot
589 	     * if we acquired it implicitly.
590 	     */
591 
592 	    if (dispPtr->implicitWinPtr == winPtr) {
593 		if (focusDebug) {
594 		    printf("releasing focus to root after %s died\n",
595 			    focusPtr->topLevelPtr->pathName);
596 		}
597 		dispPtr->implicitWinPtr = NULL;
598 		dispPtr->focusWinPtr = NULL;
599 	    }
600 	    if (dispPtr->focusWinPtr == focusPtr->focusWinPtr) {
601 		dispPtr->focusWinPtr = NULL;
602 	    }
603 	    if (dispPtr->focusOnMapPtr == focusPtr->topLevelPtr) {
604 		dispPtr->focusOnMapPtr = NULL;
605 	    }
606 	    if (prevPtr == NULL) {
607 		winPtr->mainPtr->focusPtr = focusPtr->nextPtr;
608 	    } else {
609 		prevPtr->nextPtr = focusPtr->nextPtr;
610 	    }
611 	    ckfree((char *) focusPtr);
612 	    break;
613 	} else if (winPtr == focusPtr->focusWinPtr) {
614 	    /*
615 	     * The deleted window had the focus for its top-level:
616 	     * move the focus to the top-level itself.
617 	     */
618 
619 	    focusPtr->focusWinPtr = focusPtr->topLevelPtr;
620 	    if ((dispPtr->focusWinPtr == winPtr)
621 		    && !(focusPtr->topLevelPtr->flags & TK_ALREADY_DEAD)) {
622 		if (focusDebug) {
623 		    printf("forwarding focus to %s after %s died\n",
624 			    focusPtr->topLevelPtr->pathName, winPtr->pathName);
625 		}
626 		GenerateFocusEvents(dispPtr->focusWinPtr,
627 			focusPtr->topLevelPtr);
628 		dispPtr->focusWinPtr = focusPtr->topLevelPtr;
629 	    }
630 	    break;
631 	}
632     }
633 
634     if (winPtr->mainPtr->lastFocusPtr == winPtr) {
635 	winPtr->mainPtr->lastFocusPtr = NULL;
636     }
637 }
638 
639 /*
640  *----------------------------------------------------------------------
641  *
642  * GenerateFocusEvents --
643  *
644  *	This procedure is called to create FocusIn and FocusOut events to
645  *	move the input focus from one window to another.
646  *
647  * Results:
648  *	None.
649  *
650  * Side effects:
651  *	FocusIn and FocusOut events are generated.
652  *
653  *----------------------------------------------------------------------
654  */
655 
656 static void
GenerateFocusEvents(sourcePtr,destPtr)657 GenerateFocusEvents(sourcePtr, destPtr)
658     TkWindow *sourcePtr;	/* Window that used to have the focus (may
659 				 * be NULL). */
660     TkWindow *destPtr;		/* New window to have the focus (may be
661 				 * NULL). */
662 
663 {
664     XEvent event;
665     TkWindow *winPtr;
666 
667     winPtr = sourcePtr;
668     if (winPtr == NULL) {
669 	winPtr = destPtr;
670 	if (winPtr == NULL) {
671 	    return;
672 	}
673     }
674 
675     event.xfocus.serial = LastKnownRequestProcessed(winPtr->display);
676     event.xfocus.send_event = GENERATED_EVENT_MAGIC;
677     event.xfocus.display = winPtr->display;
678     event.xfocus.mode = NotifyNormal;
679     TkInOutEvents(&event, sourcePtr, destPtr, FocusOut, FocusIn,
680 	    TCL_QUEUE_MARK);
681 }
682 
683 /*
684  *----------------------------------------------------------------------
685  *
686  * ChangeXFocus --
687  *
688  *	This procedure is invoked to move the official X focus from
689  *	one top-level to another.  We do this when the application
690  *	changes the focus window from one top-level to another, in
691  *	order to notify the window manager so that it can highlight
692  *	the new focus top-level.
693  *
694  * Results:
695  *	None.
696  *
697  * Side effects:
698  *	The official X focus window changes;  the application's focus
699  *	window isn't changed by this procedure.
700  *
701  *----------------------------------------------------------------------
702  */
703 
704 static void
ChangeXFocus(topLevelPtr,force)705 ChangeXFocus(topLevelPtr, force)
706     TkWindow *topLevelPtr;	/* Top-level window that is to receive
707 				 * the X focus. */
708     int force;			/* Non-zero means claim the focus even
709 				 * if it didn't originally belong to
710 				 * topLevelPtr's application. */
711 {
712     TkDisplay *dispPtr = topLevelPtr->dispPtr;
713     TkWindow *winPtr;
714     Window focusWindow;
715     int dummy;
716     Tk_ErrorHandler errHandler;
717 
718     /*
719      * If the focus was received implicitly, then there's no advantage
720      * in setting an explicit focus;  just return.
721      */
722 
723     if (dispPtr->implicitWinPtr != NULL) {
724 	return;
725     }
726 
727     /*
728      * Check to make sure that the focus is still in one of the
729      * windows of this application.  Furthermore, grab the server
730      * to make sure that the focus doesn't change in the middle
731      * of this operation.
732      */
733 
734     if (!focusDebug) {
735 	XGrabServer(dispPtr->display);
736     }
737     if (!force) {
738 	XGetInputFocus(dispPtr->display, &focusWindow, &dummy);
739 	winPtr = (TkWindow *) Tk_IdToWindow(dispPtr->display, focusWindow);
740 	if ((winPtr == NULL) || (winPtr->mainPtr != topLevelPtr->mainPtr)) {
741 	    goto done;
742 	}
743     }
744 
745     /*
746      * Tell X to change the focus.  Ignore errors that occur when changing
747      * the focus:  it is still possible that the window we're focussing
748      * to could have gotten unmapped, which will generate an error.
749      */
750 
751     errHandler = Tk_CreateErrorHandler(dispPtr->display, -1, -1, -1,
752 	    (Tk_ErrorProc *) NULL, (ClientData) NULL);
753     XSetInputFocus(dispPtr->display, topLevelPtr->window, RevertToParent,
754 	    CurrentTime);
755     Tk_DeleteErrorHandler(errHandler);
756     if (focusDebug) {
757 	printf("Set X focus to %s\n", topLevelPtr->pathName);
758     }
759 
760     done:
761     if (!focusDebug) {
762 	XUngrabServer(dispPtr->display);
763     }
764 }
765 
766 /*
767  *----------------------------------------------------------------------
768  *
769  * FocusMapProc --
770  *
771  *	This procedure is called as an event handler for StructureNotify
772  *	events, if a window receives the focus at a time when its
773  *	toplevel isn't mapped.  The procedure is needed because X
774  *	won't allow the focus to be set to an unmapped window;  we
775  *	detect when the toplevel is mapped and set the focus to it then.
776  *
777  * Results:
778  *	None.
779  *
780  * Side effects:
781  *	If this is a map event, the focus gets set to the toplevel
782  *	given by clientData.
783  *
784  *----------------------------------------------------------------------
785  */
786 
787 static void
FocusMapProc(clientData,eventPtr)788 FocusMapProc(clientData, eventPtr)
789     ClientData clientData;	/* Toplevel window. */
790     XEvent *eventPtr;		/* Information about event. */
791 {
792     TkWindow *winPtr = (TkWindow *) clientData;
793     TkDisplay *dispPtr = winPtr->dispPtr;
794 
795     if (eventPtr->type == MapNotify) {
796 	ChangeXFocus(winPtr, dispPtr->forceFocus);
797 	Tk_DeleteEventHandler((Tk_Window) winPtr, StructureNotifyMask,
798 		FocusMapProc, clientData);
799 	dispPtr->focusOnMapPtr = NULL;
800     }
801 }
802