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