1/*
2 * tkUnixEmbed.c --
3 *
4 *	This file contains platform-specific functions for UNIX to provide
5 *	basic operations needed for application embedding (where one
6 *	application can use as its main window an internal window from some
7 *	other application).
8 *
9 * Copyright (c) 1996-1997 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 "tkUnixInt.h"
18
19/*
20 * One of the following structures exists for each container in this
21 * application. It keeps track of the container window and its associated
22 * embedded window.
23 */
24
25typedef struct Container {
26    Window parent;		/* X's window id for the parent of the pair
27				 * (the container). */
28    Window parentRoot;		/* Id for the root window of parent's
29				 * screen. */
30    TkWindow *parentPtr;	/* Tk's information about the container, or
31				 * NULL if the container isn't in this
32				 * process. */
33    Window wrapper;		/* X's window id for the wrapper window for
34				 * the embedded window. Starts off as None,
35				 * but gets filled in when the window is
36				 * eventually created. */
37    TkWindow *embeddedPtr;	/* Tk's information about the embedded window,
38				 * or NULL if the embedded application isn't
39				 * in this process. Note that this is *not*
40				 * the same window as wrapper: wrapper is the
41				 * parent of embeddedPtr. */
42    struct Container *nextPtr;	/* Next in list of all containers in this
43				 * process. */
44} Container;
45
46typedef struct ThreadSpecificData {
47    Container *firstContainerPtr;
48				/* First in list of all containers managed by
49				 * this process. */
50} ThreadSpecificData;
51static Tcl_ThreadDataKey dataKey;
52
53/*
54 * Prototypes for static functions defined in this file:
55 */
56
57static void		ContainerEventProc(ClientData clientData,
58			    XEvent *eventPtr);
59static void		EmbeddedEventProc(ClientData clientData,
60			    XEvent *eventPtr);
61static int		EmbedErrorProc(ClientData clientData,
62			    XErrorEvent *errEventPtr);
63static void		EmbedFocusProc(ClientData clientData,
64			    XEvent *eventPtr);
65static void		EmbedGeometryRequest(Container *containerPtr,
66			    int width, int height);
67static void		EmbedSendConfigure(Container *containerPtr);
68static void		EmbedStructureProc(ClientData clientData,
69			    XEvent *eventPtr);
70static void		EmbedWindowDeleted(TkWindow *winPtr);
71
72/*
73 *----------------------------------------------------------------------
74 *
75 * TkpUseWindow --
76 *
77 *	This function causes a Tk window to use a given X window as its parent
78 *	window, rather than the root window for the screen. It is invoked by
79 *	an embedded application to specify the window in which it is embedded.
80 *
81 * Results:
82 *	The return value is normally TCL_OK. If an error occurs (such as
83 *	string not being a valid window spec), then the return value is
84 *	TCL_ERROR and an error message is left in the interp's result if
85 *	interp is non-NULL.
86 *
87 * Side effects:
88 *	Changes the colormap and other visual information to match that of the
89 *	parent window given by "string".
90 *
91 *----------------------------------------------------------------------
92 */
93
94int
95TkpUseWindow(
96    Tcl_Interp *interp,		/* If not NULL, used for error reporting if
97				 * string is bogus. */
98    Tk_Window tkwin,		/* Tk window that does not yet have an
99				 * associated X window. */
100    CONST char *string)		/* String identifying an X window to use for
101				 * tkwin; must be an integer value. */
102{
103    TkWindow *winPtr = (TkWindow *) tkwin;
104    TkWindow *usePtr;
105    int id, anyError;
106    Window parent;
107    Tk_ErrorHandler handler;
108    Container *containerPtr;
109    XWindowAttributes parentAtts;
110    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
111            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
112
113    if (winPtr->window != None) {
114	Tcl_AppendResult(interp,
115		"can't modify container after widget is created", NULL);
116	return TCL_ERROR;
117    }
118    if (Tcl_GetInt(interp, string, &id) != TCL_OK) {
119	return TCL_ERROR;
120    }
121    parent = (Window) id;
122
123    usePtr = (TkWindow *) Tk_IdToWindow(winPtr->display, parent);
124    if (usePtr != NULL) {
125	if (!(usePtr->flags & TK_CONTAINER)) {
126	    Tcl_AppendResult(interp, "window \"", usePtr->pathName,
127                    "\" doesn't have -container option set", NULL);
128	    return TCL_ERROR;
129	}
130    }
131
132    /*
133     * Tk sets the window colormap to the screen default colormap in
134     * tkWindow.c:AllocWindow. This doesn't work well for embedded windows. So
135     * we override the colormap and visual settings to be the same as the
136     * parent window (which is in the container app).
137     */
138
139    anyError = 0;
140    handler = Tk_CreateErrorHandler(winPtr->display, -1, -1, -1,
141	    EmbedErrorProc, (ClientData) &anyError);
142    if (!XGetWindowAttributes(winPtr->display, parent, &parentAtts)) {
143        anyError = 1;
144    }
145    XSync(winPtr->display, False);
146    Tk_DeleteErrorHandler(handler);
147    if (anyError) {
148	if (interp != NULL) {
149	    Tcl_AppendResult(interp, "couldn't create child of window \"",
150		    string, "\"", NULL);
151	}
152	return TCL_ERROR;
153    }
154    Tk_SetWindowVisual(tkwin, parentAtts.visual, parentAtts.depth,
155	    parentAtts.colormap);
156
157    /*
158     * Create an event handler to clean up the Container structure when tkwin
159     * is eventually deleted.
160     */
161
162    Tk_CreateEventHandler(tkwin, StructureNotifyMask, EmbeddedEventProc,
163	    (ClientData) winPtr);
164
165    /*
166     * Save information about the container and the embedded window in a
167     * Container structure. If there is already an existing Container
168     * structure, it means that both container and embedded app. are in the
169     * same process.
170     */
171
172    for (containerPtr = tsdPtr->firstContainerPtr; containerPtr != NULL;
173	    containerPtr = containerPtr->nextPtr) {
174	if (containerPtr->parent == parent) {
175	    winPtr->flags |= TK_BOTH_HALVES;
176	    containerPtr->parentPtr->flags |= TK_BOTH_HALVES;
177	    break;
178	}
179    }
180    if (containerPtr == NULL) {
181	containerPtr = (Container *) ckalloc(sizeof(Container));
182	containerPtr->parent = parent;
183	containerPtr->parentRoot = parentAtts.root;
184	containerPtr->parentPtr = NULL;
185	containerPtr->wrapper = None;
186	containerPtr->nextPtr = tsdPtr->firstContainerPtr;
187	tsdPtr->firstContainerPtr = containerPtr;
188    }
189    containerPtr->embeddedPtr = winPtr;
190    winPtr->flags |= TK_EMBEDDED;
191    return TCL_OK;
192}
193
194/*
195 *----------------------------------------------------------------------
196 *
197 * TkpMakeWindow --
198 *
199 *	Create an actual window system window object based on the current
200 *	attributes of the specified TkWindow.
201 *
202 * Results:
203 *	Returns the handle to the new window, or None on failure.
204 *
205 * Side effects:
206 *	Creates a new X window.
207 *
208 *----------------------------------------------------------------------
209 */
210
211Window
212TkpMakeWindow(
213    TkWindow *winPtr,		/* Tk's information about the window that is
214				 * to be instantiated. */
215    Window parent)		/* Window system token for the parent in which
216				 * the window is to be created. */
217{
218    Container *containerPtr;
219    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
220            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
221
222    if (winPtr->flags & TK_EMBEDDED) {
223	/*
224	 * This window is embedded. Don't create the new window in the given
225	 * parent; instead, create it as a child of the root window of the
226	 * container's screen. The window will get reparented into a wrapper
227	 * window later.
228	 */
229
230	for (containerPtr = tsdPtr->firstContainerPtr; ;
231		containerPtr = containerPtr->nextPtr) {
232	    if (containerPtr == NULL) {
233		Tcl_Panic("TkMakeWindow couldn't find container for window");
234	    }
235	    if (containerPtr->embeddedPtr == winPtr) {
236		break;
237	    }
238	}
239	parent = containerPtr->parentRoot;
240    }
241
242    return XCreateWindow(winPtr->display, parent, winPtr->changes.x,
243	    winPtr->changes.y, (unsigned) winPtr->changes.width,
244	    (unsigned) winPtr->changes.height,
245	    (unsigned) winPtr->changes.border_width, winPtr->depth,
246	    InputOutput, winPtr->visual, winPtr->dirtyAtts,
247	    &winPtr->atts);
248}
249
250/*
251 *----------------------------------------------------------------------
252 *
253 * TkpMakeContainer --
254 *
255 *	This function is called to indicate that a particular window will be a
256 *	container for an embedded application. This changes certain aspects of
257 *	the window's behavior, such as whether it will receive events anymore.
258 *
259 * Results:
260 *	None.
261 *
262 * Side effects:
263 *	None.
264 *
265 *----------------------------------------------------------------------
266 */
267
268void
269TkpMakeContainer(
270    Tk_Window tkwin)		/* Token for a window that is about to become
271				 * a container. */
272{
273    TkWindow *winPtr = (TkWindow *) tkwin;
274    Container *containerPtr;
275    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
276            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
277
278    /*
279     * Register the window as a container so that, for example, we can find
280     * out later if the embedded app. is in the same process.
281     */
282
283    Tk_MakeWindowExist(tkwin);
284    containerPtr = (Container *) ckalloc(sizeof(Container));
285    containerPtr->parent = Tk_WindowId(tkwin);
286    containerPtr->parentRoot = RootWindowOfScreen(Tk_Screen(tkwin));
287    containerPtr->parentPtr = winPtr;
288    containerPtr->wrapper = None;
289    containerPtr->embeddedPtr = NULL;
290    containerPtr->nextPtr = tsdPtr->firstContainerPtr;
291    tsdPtr->firstContainerPtr = containerPtr;
292    winPtr->flags |= TK_CONTAINER;
293
294    /*
295     * Request SubstructureNotify events so that we can find out when the
296     * embedded application creates its window or attempts to resize it. Also
297     * watch Configure events on the container so that we can resize the child
298     * to match.
299     */
300
301    winPtr->atts.event_mask |= SubstructureRedirectMask|SubstructureNotifyMask;
302    XSelectInput(winPtr->display, winPtr->window, winPtr->atts.event_mask);
303    Tk_CreateEventHandler(tkwin,
304	    SubstructureNotifyMask|SubstructureRedirectMask,
305	    ContainerEventProc, (ClientData) winPtr);
306    Tk_CreateEventHandler(tkwin, StructureNotifyMask, EmbedStructureProc,
307	    (ClientData) containerPtr);
308    Tk_CreateEventHandler(tkwin, FocusChangeMask, EmbedFocusProc,
309	    (ClientData) containerPtr);
310}
311
312/*
313 *----------------------------------------------------------------------
314 *
315 * EmbedErrorProc --
316 *
317 *	This function is invoked if an error occurs while creating an embedded
318 *	window.
319 *
320 * Results:
321 *	Always returns 0 to indicate that the error has been properly handled.
322 *
323 * Side effects:
324 *	The integer pointed to by the clientData argument is set to 1.
325 *
326 *----------------------------------------------------------------------
327 */
328
329static int
330EmbedErrorProc(
331    ClientData clientData,	/* Points to integer to set. */
332    XErrorEvent *errEventPtr)	/* Points to information about error (not
333				 * used). */
334{
335    int *iPtr = (int *) clientData;
336
337    *iPtr = 1;
338    return 0;
339}
340
341/*
342 *----------------------------------------------------------------------
343 *
344 * EmbeddedEventProc --
345 *
346 *	This function is invoked by the Tk event dispatcher when various
347 *	useful events are received for a window that is embedded in another
348 *	application.
349 *
350 * Results:
351 *	None.
352 *
353 * Side effects:
354 *	Our internal state gets cleaned up when an embedded window is
355 *	destroyed.
356 *
357 *----------------------------------------------------------------------
358 */
359
360static void
361EmbeddedEventProc(
362    ClientData clientData,	/* Token for container window. */
363    XEvent *eventPtr)		/* ResizeRequest event. */
364{
365    TkWindow *winPtr = (TkWindow *) clientData;
366
367    if (eventPtr->type == DestroyNotify) {
368	EmbedWindowDeleted(winPtr);
369    }
370}
371
372/*
373 *----------------------------------------------------------------------
374 *
375 * ContainerEventProc --
376 *
377 *	This function is invoked by the Tk event dispatcher when various
378 *	useful events are received for the children of a container window. It
379 *	forwards relevant information, such as geometry requests, from the
380 *	events into the container's application.
381 *
382 * Results:
383 *	None.
384 *
385 * Side effects:
386 *	Depends on the event. For example, when ConfigureRequest events occur,
387 *	geometry information gets set for the container window.
388 *
389 *----------------------------------------------------------------------
390 */
391
392static void
393ContainerEventProc(
394    ClientData clientData,	/* Token for container window. */
395    XEvent *eventPtr)		/* ResizeRequest event. */
396{
397    TkWindow *winPtr = (TkWindow *) clientData;
398    Container *containerPtr;
399    Tk_ErrorHandler errHandler;
400    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
401            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
402
403    /*
404     * Ignore any X protocol errors that happen in this function (almost any
405     * operation could fail, for example, if the embedded application has
406     * deleted its window).
407     */
408
409    errHandler = Tk_CreateErrorHandler(eventPtr->xfocus.display, -1,
410	    -1, -1, NULL, (ClientData) NULL);
411
412    /*
413     * Find the Container structure associated with the parent window.
414     */
415
416    for (containerPtr = tsdPtr->firstContainerPtr;
417	    containerPtr->parent != eventPtr->xmaprequest.parent;
418	    containerPtr = containerPtr->nextPtr) {
419	if (containerPtr == NULL) {
420	    Tcl_Panic("ContainerEventProc couldn't find Container record");
421	}
422    }
423
424    if (eventPtr->type == CreateNotify) {
425	/*
426	 * A new child window has been created in the container. Record its id
427	 * in the Container structure (if more than one child is created, just
428	 * remember the last one and ignore the earlier ones). Also set the
429	 * child's size to match the container.
430	 */
431
432	containerPtr->wrapper = eventPtr->xcreatewindow.window;
433	XMoveResizeWindow(eventPtr->xcreatewindow.display,
434		containerPtr->wrapper, 0, 0,
435		(unsigned) Tk_Width((Tk_Window) containerPtr->parentPtr),
436		(unsigned) Tk_Height((Tk_Window) containerPtr->parentPtr));
437    } else if (eventPtr->type == ConfigureRequest) {
438	if ((eventPtr->xconfigurerequest.x != 0)
439		|| (eventPtr->xconfigurerequest.y != 0)) {
440	    /*
441	     * The embedded application is trying to move itself, which isn't
442	     * legal. At this point, the window hasn't actually moved, but we
443	     * need to send it a ConfigureNotify event to let it know that its
444	     * request has been denied. If the embedded application was also
445	     * trying to resize itself, a ConfigureNotify will be sent by the
446	     * geometry management code below, so we don't need to do
447	     * anything. Otherwise, generate a synthetic event.
448	     */
449
450	    if ((eventPtr->xconfigurerequest.width == winPtr->changes.width)
451		    && (eventPtr->xconfigurerequest.height
452		    == winPtr->changes.height)) {
453		EmbedSendConfigure(containerPtr);
454	    }
455	}
456	EmbedGeometryRequest(containerPtr,
457		eventPtr->xconfigurerequest.width,
458		eventPtr->xconfigurerequest.height);
459    } else if (eventPtr->type == MapRequest) {
460	/*
461	 * The embedded application's map request was ignored and simply
462	 * passed on to us, so we have to map the window for it to appear on
463	 * the screen.
464	 */
465
466	XMapWindow(eventPtr->xmaprequest.display,
467		eventPtr->xmaprequest.window);
468    } else if (eventPtr->type == DestroyNotify) {
469	/*
470	 * The embedded application is gone. Destroy the container window.
471	 */
472
473	Tk_DestroyWindow((Tk_Window) winPtr);
474    }
475    Tk_DeleteErrorHandler(errHandler);
476}
477
478/*
479 *----------------------------------------------------------------------
480 *
481 * EmbedStructureProc --
482 *
483 *	This function is invoked by the Tk event dispatcher when a container
484 *	window owned by this application gets resized (and also at several
485 *	other times that we don't care about). This function reflects the size
486 *	change in the embedded window that corresponds to the container.
487 *
488 * Results:
489 *	None.
490 *
491 * Side effects:
492 *	The embedded window gets resized to match the container.
493 *
494 *----------------------------------------------------------------------
495 */
496
497static void
498EmbedStructureProc(
499    ClientData clientData,	/* Token for container window. */
500    XEvent *eventPtr)		/* ResizeRequest event. */
501{
502    Container *containerPtr = (Container *) clientData;
503    Tk_ErrorHandler errHandler;
504
505    if (eventPtr->type == ConfigureNotify) {
506	if (containerPtr->wrapper != None) {
507	    /*
508	     * Ignore errors, since the embedded application could have
509	     * deleted its window.
510	     */
511
512	    errHandler = Tk_CreateErrorHandler(eventPtr->xfocus.display, -1,
513		    -1, -1, NULL, (ClientData) NULL);
514	    XMoveResizeWindow(eventPtr->xconfigure.display,
515		    containerPtr->wrapper, 0, 0,
516		    (unsigned) Tk_Width((Tk_Window) containerPtr->parentPtr),
517		    (unsigned) Tk_Height((Tk_Window) containerPtr->parentPtr));
518	    Tk_DeleteErrorHandler(errHandler);
519	}
520    } else if (eventPtr->type == DestroyNotify) {
521	EmbedWindowDeleted(containerPtr->parentPtr);
522    }
523}
524
525/*
526 *----------------------------------------------------------------------
527 *
528 * EmbedFocusProc --
529 *
530 *	This function is invoked by the Tk event dispatcher when FocusIn and
531 *	FocusOut events occur for a container window owned by this
532 *	application. It is responsible for moving the focus back and forth
533 *	between a container application and an embedded application.
534 *
535 * Results:
536 *	None.
537 *
538 * Side effects:
539 *	The X focus may change.
540 *
541 *----------------------------------------------------------------------
542 */
543
544static void
545EmbedFocusProc(
546    ClientData clientData,	/* Token for container window. */
547    XEvent *eventPtr)		/* ResizeRequest event. */
548{
549    Container *containerPtr = (Container *) clientData;
550    Tk_ErrorHandler errHandler;
551    Display *display;
552
553    display = Tk_Display(containerPtr->parentPtr);
554    if (eventPtr->type == FocusIn) {
555	/*
556	 * The focus just arrived at the container. Change the X focus to move
557	 * it to the embedded application, if there is one. Ignore X errors
558	 * that occur during this operation (it's possible that the new focus
559	 * window isn't mapped).
560	 */
561
562	if (containerPtr->wrapper != None) {
563	    errHandler = Tk_CreateErrorHandler(eventPtr->xfocus.display, -1,
564		    -1, -1, NULL, (ClientData) NULL);
565	    XSetInputFocus(display, containerPtr->wrapper, RevertToParent,
566		    CurrentTime);
567	    Tk_DeleteErrorHandler(errHandler);
568	}
569    }
570}
571
572/*
573 *----------------------------------------------------------------------
574 *
575 * EmbedGeometryRequest --
576 *
577 *	This function is invoked when an embedded application requests a
578 *	particular size. It processes the request (which may or may not
579 *	actually honor the request) and reflects the results back to the
580 *	embedded application.
581 *
582 * Results:
583 *	None.
584 *
585 * Side effects:
586 *	If we deny the child's size change request, a Configure event is
587 *	synthesized to let the child know how big it ought to be. Events get
588 *	processed while we're waiting for the geometry managers to do their
589 *	thing.
590 *
591 *----------------------------------------------------------------------
592 */
593
594static void
595EmbedGeometryRequest(
596    Container *containerPtr,	/* Information about the embedding. */
597    int width, int height)	/* Size that the child has requested. */
598{
599    TkWindow *winPtr = containerPtr->parentPtr;
600
601    /*
602     * Forward the requested size into our geometry management hierarchy via
603     * the container window. We need to send a Configure event back to the
604     * embedded application if we decide not to honor its request; to make
605     * this happen, process all idle event handlers synchronously here (so
606     * that the geometry managers have had a chance to do whatever they want
607     * to do), and if the window's size didn't change then generate a
608     * configure event.
609     */
610
611    Tk_GeometryRequest((Tk_Window) winPtr, width, height);
612    while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {
613	/* Empty loop body. */
614    }
615    if ((winPtr->changes.width != width)
616	    || (winPtr->changes.height != height)) {
617	EmbedSendConfigure(containerPtr);
618    }
619}
620
621/*
622 *----------------------------------------------------------------------
623 *
624 * EmbedSendConfigure --
625 *
626 *	This function synthesizes a ConfigureNotify event to notify an
627 *	embedded application of its current size and location. This function
628 *	is called when the embedded application made a geometry request that
629 *	we did not grant, so that the embedded application knows that its
630 *	geometry didn't change after all.
631 *
632 * Results:
633 *	None.
634 *
635 * Side effects:
636 *	None.
637 *
638 *----------------------------------------------------------------------
639 */
640
641static void
642EmbedSendConfigure(
643    Container *containerPtr)	/* Information about the embedding. */
644{
645    TkWindow *winPtr = containerPtr->parentPtr;
646    XEvent event;
647
648    event.xconfigure.type = ConfigureNotify;
649    event.xconfigure.serial = LastKnownRequestProcessed(winPtr->display);
650    event.xconfigure.send_event = True;
651    event.xconfigure.display = winPtr->display;
652    event.xconfigure.event = containerPtr->wrapper;
653    event.xconfigure.window = containerPtr->wrapper;
654    event.xconfigure.x = 0;
655    event.xconfigure.y = 0;
656    event.xconfigure.width = winPtr->changes.width;
657    event.xconfigure.height = winPtr->changes.height;
658    event.xconfigure.above = None;
659    event.xconfigure.override_redirect = False;
660
661    /*
662     * Note: when sending the event below, the ButtonPressMask causes the
663     * event to be sent only to applications that have selected for
664     * ButtonPress events, which should be just the embedded application.
665     */
666
667    XSendEvent(winPtr->display, containerPtr->wrapper, False,
668	    0, &event);
669
670    /*
671     * The following needs to be done if the embedded window is not in the
672     * same application as the container window.
673     */
674
675    if (containerPtr->embeddedPtr == NULL) {
676	XMoveResizeWindow(winPtr->display, containerPtr->wrapper, 0, 0,
677		(unsigned) winPtr->changes.width,
678		(unsigned) winPtr->changes.height);
679    }
680}
681
682/*
683 *----------------------------------------------------------------------
684 *
685 * TkpGetOtherWindow --
686 *
687 *	If both the container and embedded window are in the same process,
688 *	this function will return either one, given the other.
689 *
690 * Results:
691 *	If winPtr is a container, the return value is the token for the
692 *	embedded window, and vice versa. If the "other" window isn't in this
693 *	process, NULL is returned.
694 *
695 * Side effects:
696 *	None.
697 *
698 *----------------------------------------------------------------------
699 */
700
701TkWindow *
702TkpGetOtherWindow(
703    TkWindow *winPtr)		/* Tk's structure for a container or embedded
704				 * window. */
705{
706    Container *containerPtr;
707    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
708            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
709
710    for (containerPtr = tsdPtr->firstContainerPtr;
711            containerPtr != NULL;
712	    containerPtr = containerPtr->nextPtr) {
713	if (containerPtr->embeddedPtr == winPtr) {
714	    return containerPtr->parentPtr;
715	} else if (containerPtr->parentPtr == winPtr) {
716	    return containerPtr->embeddedPtr;
717	}
718    }
719    return NULL;
720}
721
722/*
723 *----------------------------------------------------------------------
724 *
725 * TkpRedirectKeyEvent --
726 *
727 *	This function is invoked when a key press or release event arrives for
728 *	an application that does not believe it owns the input focus. This can
729 *	happen because of embedding; for example, X can send an event to an
730 *	embedded application when the real focus window is in the container
731 *	application and is an ancestor of the container. This function's job
732 *	is to forward the event back to the application where it really
733 *	belongs.
734 *
735 * Results:
736 *	None.
737 *
738 * Side effects:
739 *	The event may get sent to a different application.
740 *
741 *----------------------------------------------------------------------
742 */
743
744void
745TkpRedirectKeyEvent(
746    TkWindow *winPtr,		/* Window to which the event was originally
747				 * reported. */
748    XEvent *eventPtr)		/* X event to redirect (should be KeyPress or
749				 * KeyRelease). */
750{
751    Container *containerPtr;
752    Window saved;
753    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
754            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
755
756    /*
757     * First, find the top-level window corresponding to winPtr.
758     */
759
760    while (1) {
761	if (winPtr == NULL) {
762	    /*
763	     * This window is being deleted. This is too confusing a case to
764	     * handle so discard the event.
765	     */
766
767	    return;
768	}
769	if (winPtr->flags & TK_TOP_HIERARCHY) {
770	    break;
771	}
772	winPtr = winPtr->parentPtr;
773    }
774
775    if (winPtr->flags & TK_EMBEDDED) {
776	/*
777	 * This application is embedded. If we got a key event without
778	 * officially having the focus, it means that the focus is really in
779	 * the container, but the mouse was over the embedded application.
780	 * Send the event back to the container.
781	 */
782
783	for (containerPtr = tsdPtr->firstContainerPtr;
784		containerPtr->embeddedPtr != winPtr;
785		containerPtr = containerPtr->nextPtr) {
786	    /* Empty loop body. */
787	}
788	saved = eventPtr->xkey.window;
789	eventPtr->xkey.window = containerPtr->parent;
790	XSendEvent(eventPtr->xkey.display, eventPtr->xkey.window, False,
791		KeyPressMask|KeyReleaseMask, eventPtr);
792	eventPtr->xkey.window = saved;
793    }
794}
795
796/*
797 *----------------------------------------------------------------------
798 *
799 * TkpClaimFocus --
800 *
801 *	This function is invoked when someone asks or the input focus to be
802 *	put on a window in an embedded application, but the application
803 *	doesn't currently have the focus. It requests the input focus from the
804 *	container application.
805 *
806 * Results:
807 *	None.
808 *
809 * Side effects:
810 *	The input focus may change.
811 *
812 *----------------------------------------------------------------------
813 */
814
815void
816TkpClaimFocus(
817    TkWindow *topLevelPtr,	/* Top-level window containing desired focus
818				 * window; should be embedded. */
819    int force)			/* One means that the container should claim
820				 * the focus if it doesn't currently have
821				 * it. */
822{
823    XEvent event;
824    Container *containerPtr;
825    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
826            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
827
828    if (!(topLevelPtr->flags & TK_EMBEDDED)) {
829	return;
830    }
831
832    for (containerPtr = tsdPtr->firstContainerPtr;
833	    containerPtr->embeddedPtr != topLevelPtr;
834	    containerPtr = containerPtr->nextPtr) {
835	/* Empty loop body. */
836    }
837
838    event.xfocus.type = FocusIn;
839    event.xfocus.serial = LastKnownRequestProcessed(topLevelPtr->display);
840    event.xfocus.send_event = 1;
841    event.xfocus.display = topLevelPtr->display;
842    event.xfocus.window = containerPtr->parent;
843    event.xfocus.mode = EMBEDDED_APP_WANTS_FOCUS;
844    event.xfocus.detail = force;
845    XSendEvent(event.xfocus.display, event.xfocus.window, False, 0, &event);
846}
847
848/*
849 *----------------------------------------------------------------------
850 *
851 * TkpTestembedCmd --
852 *
853 *	This function implements the "testembed" command. It returns some or
854 *	all of the information in the list pointed to by firstContainerPtr.
855 *
856 * Results:
857 *	A standard Tcl result.
858 *
859 * Side effects:
860 *	None.
861 *
862 *----------------------------------------------------------------------
863 */
864
865int
866TkpTestembedCmd(
867    ClientData clientData,	/* Main window for application. */
868    Tcl_Interp *interp,		/* Current interpreter. */
869    int argc,			/* Number of arguments. */
870    CONST char **argv)		/* Argument strings. */
871{
872    int all;
873    Container *containerPtr;
874    Tcl_DString dString;
875    char buffer[50];
876    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
877            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
878
879    if ((argc > 1) && (strcmp(argv[1], "all") == 0)) {
880	all = 1;
881    } else {
882	all = 0;
883    }
884    Tcl_DStringInit(&dString);
885    for (containerPtr = tsdPtr->firstContainerPtr; containerPtr != NULL;
886	    containerPtr = containerPtr->nextPtr) {
887	Tcl_DStringStartSublist(&dString);
888	if (containerPtr->parent == None) {
889	    Tcl_DStringAppendElement(&dString, "");
890	} else if (all) {
891	    sprintf(buffer, "0x%x", (int) containerPtr->parent);
892	    Tcl_DStringAppendElement(&dString, buffer);
893	} else {
894	    Tcl_DStringAppendElement(&dString, "XXX");
895	}
896	if (containerPtr->parentPtr == NULL) {
897	    Tcl_DStringAppendElement(&dString, "");
898	} else {
899	    Tcl_DStringAppendElement(&dString,
900		    containerPtr->parentPtr->pathName);
901	}
902	if (containerPtr->wrapper == None) {
903	    Tcl_DStringAppendElement(&dString, "");
904	} else if (all) {
905	    sprintf(buffer, "0x%x", (int) containerPtr->wrapper);
906	    Tcl_DStringAppendElement(&dString, buffer);
907	} else {
908	    Tcl_DStringAppendElement(&dString, "XXX");
909	}
910	if (containerPtr->embeddedPtr == NULL) {
911	    Tcl_DStringAppendElement(&dString, "");
912	} else {
913	    Tcl_DStringAppendElement(&dString,
914		    containerPtr->embeddedPtr->pathName);
915	}
916	Tcl_DStringEndSublist(&dString);
917    }
918    Tcl_DStringResult(interp, &dString);
919    return TCL_OK;
920}
921
922/*
923 *----------------------------------------------------------------------
924 *
925 * EmbedWindowDeleted --
926 *
927 *	This function is invoked when a window involved in embedding (as
928 *	either the container or the embedded application) is destroyed. It
929 *	cleans up the Container structure for the window.
930 *
931 * Results:
932 *	None.
933 *
934 * Side effects:
935 *	A Container structure may be freed.
936 *
937 *----------------------------------------------------------------------
938 */
939
940static void
941EmbedWindowDeleted(
942    TkWindow *winPtr)		/* Tk's information about window that was
943				 * deleted. */
944{
945    Container *containerPtr, *prevPtr;
946    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
947            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
948
949    /*
950     * Find the Container structure for this window work. Delete the
951     * information about the embedded application and free the container's
952     * record.
953     */
954
955    prevPtr = NULL;
956    containerPtr = tsdPtr->firstContainerPtr;
957    while (1) {
958	if (containerPtr->embeddedPtr == winPtr) {
959	    containerPtr->wrapper = None;
960	    containerPtr->embeddedPtr = NULL;
961	    break;
962	}
963	if (containerPtr->parentPtr == winPtr) {
964	    containerPtr->parentPtr = NULL;
965	    break;
966	}
967	prevPtr = containerPtr;
968	containerPtr = containerPtr->nextPtr;
969    }
970    if ((containerPtr->embeddedPtr == NULL)
971	    && (containerPtr->parentPtr == NULL)) {
972	if (prevPtr == NULL) {
973	    tsdPtr->firstContainerPtr = containerPtr->nextPtr;
974	} else {
975	    prevPtr->nextPtr = containerPtr->nextPtr;
976	}
977	ckfree((char *) containerPtr);
978    }
979}
980
981/*
982 *----------------------------------------------------------------------
983 *
984 * TkUnixContainerId --
985 *
986 *	Given an embedded window, this function returns the X window
987 *	identifier for the associated container window.
988 *
989 * Results:
990 *	The return value is the X window identifier for winPtr's container
991 *	window.
992 *
993 * Side effects:
994 *	None.
995 *
996 *----------------------------------------------------------------------
997 */
998
999Window
1000TkUnixContainerId(
1001    TkWindow *winPtr)		/* Tk's structure for an embedded window. */
1002{
1003    Container *containerPtr;
1004    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
1005            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
1006
1007    for (containerPtr = tsdPtr->firstContainerPtr;
1008            containerPtr != NULL; containerPtr = containerPtr->nextPtr) {
1009	if (containerPtr->embeddedPtr == winPtr) {
1010	    return containerPtr->parent;
1011	}
1012    }
1013    Tcl_Panic("TkUnixContainerId couldn't find window");
1014    return None;
1015}
1016
1017/*
1018 * Local Variables:
1019 * mode: c
1020 * c-basic-offset: 4
1021 * fill-column: 78
1022 * End:
1023 */
1024