1/*
2 * tkWinEmbed.c --
3 *
4 *	This file contains platform specific procedures for Windows platforms
5 *	to provide basic operations needed for application embedding (where
6 *	one application can use as its main window an internal window from
7 *	another 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: tkWinEmbed.c,v 1.7.2.2 2006/04/11 20:23:45 hobbs Exp $
15 */
16
17#include "tkWinInt.h"
18
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    HWND parentHWnd;			/* Windows HWND to the parent window */
28    TkWindow *parentPtr;		/* Tk's information about the container
29					 * or NULL if the container isn't
30					 * in this process. */
31    HWND embeddedHWnd;			/* Windows HWND to the embedded window
32					 */
33    TkWindow *embeddedPtr;		/* Tk's information about the embedded
34					 * window, or NULL if the
35					 * embedded application isn't in
36					 * this process. */
37    struct Container *nextPtr;		/* Next in list of all containers in
38					 * this process. */
39} Container;
40
41typedef struct ThreadSpecificData {
42    Container *firstContainerPtr;       /* First in list of all containers
43					 * managed by this process.  */
44} ThreadSpecificData;
45static Tcl_ThreadDataKey dataKey;
46
47static void		CleanupContainerList _ANSI_ARGS_((
48    			    ClientData clientData));
49static void		ContainerEventProc _ANSI_ARGS_((ClientData clientData,
50			    XEvent *eventPtr));
51static void		EmbeddedEventProc _ANSI_ARGS_((
52			    ClientData clientData, XEvent *eventPtr));
53static void		EmbedGeometryRequest _ANSI_ARGS_((
54    			    Container*containerPtr, int width, int height));
55static void		EmbedWindowDeleted _ANSI_ARGS_((TkWindow *winPtr));
56
57/*
58 *----------------------------------------------------------------------
59 *
60 * CleanupContainerList --
61 *
62 *	Finalizes the list of containers.
63 *
64 * Results:
65 *	None.
66 *
67 * Side effects:
68 *	Releases memory occupied by containers of embedded windows.
69 *
70 *----------------------------------------------------------------------
71 */
72
73	/* ARGSUSED */
74static void
75CleanupContainerList(clientData)
76    ClientData clientData;
77{
78    Container *nextPtr;
79    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
80            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
81
82    for (;
83        tsdPtr->firstContainerPtr != (Container *) NULL;
84        tsdPtr->firstContainerPtr = nextPtr) {
85        nextPtr = tsdPtr->firstContainerPtr->nextPtr;
86        ckfree((char *) tsdPtr->firstContainerPtr);
87    }
88    tsdPtr->firstContainerPtr = (Container *) NULL;
89}
90
91/*
92 *----------------------------------------------------------------------
93 *
94 * TkpTestembedCmd --
95 *
96 *	Test command for the embedding facility.
97 *
98 * Results:
99 *	Always returns TCL_OK.
100 *
101 * Side effects:
102 *	Currently it does not do anything.
103 *
104 *----------------------------------------------------------------------
105 */
106
107	/* ARGSUSED */
108int
109TkpTestembedCmd(clientData, interp, argc, argv)
110    ClientData clientData;
111    Tcl_Interp *interp;
112    int argc;
113    CONST char **argv;
114{
115    return TCL_OK;
116}
117
118/*
119 *----------------------------------------------------------------------
120 *
121 * TkpUseWindow --
122 *
123 *	This procedure causes a Tk window to use a given Windows handle
124 *	for a window as its underlying window, rather than a new Windows
125 *	window being created automatically. It is invoked by an embedded
126 *	application to specify the window in which the application is
127 *	embedded.
128 *
129 * Results:
130 *	The return value is normally TCL_OK. If an error occurred (such as
131 *	if the argument does not identify a legal Windows window handle),
132 *	the return value is TCL_ERROR and an error message is left in the
133 *	the interp's result if interp is not NULL.
134 *
135 * Side effects:
136 *	None.
137 *
138 *----------------------------------------------------------------------
139 */
140
141int
142TkpUseWindow(interp, tkwin, string)
143    Tcl_Interp *interp;		/* If not NULL, used for error reporting
144				 * if string is bogus. */
145    Tk_Window tkwin;		/* Tk window that does not yet have an
146				 * associated X window. */
147    CONST char *string;		/* String identifying an X window to use
148				 * for tkwin;  must be an integer value. */
149{
150    TkWindow *winPtr = (TkWindow *) tkwin;
151    TkWindow *usePtr;
152    int id;
153    HWND hwnd;
154    Container *containerPtr;
155    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
156            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
157
158    if (winPtr->window != None) {
159        panic("TkpUseWindow: Already assigned a window");
160    }
161
162    if (Tcl_GetInt(interp, string, &id) != TCL_OK) {
163        return TCL_ERROR;
164    }
165    hwnd = (HWND) id;
166
167    /*
168     * Check if the window is a valid handle. If it is invalid, return
169     * TCL_ERROR and potentially leave an error message in the interp's
170     * result.
171     */
172
173    if (!IsWindow(hwnd)) {
174        if (interp != (Tcl_Interp *) NULL) {
175            Tcl_AppendResult(interp, "window \"", string,
176                    "\" doesn't exist", (char *) NULL);
177        }
178        return TCL_ERROR;
179    }
180
181    usePtr = (TkWindow *) Tk_HWNDToWindow(hwnd);
182    if (usePtr != NULL) {
183        if (!(usePtr->flags & TK_CONTAINER)) {
184	    Tcl_AppendResult(interp, "window \"", usePtr->pathName,
185                    "\" doesn't have -container option set", (char *) NULL);
186	    return TCL_ERROR;
187	}
188    }
189
190    /*
191     * Store the parent window in the platform private data slot so
192     * TkWmMapWindow can use it when creating the wrapper window.
193     */
194
195    winPtr->privatePtr = (struct TkWindowPrivate*) hwnd;
196
197    /*
198     * Create an event handler to clean up the Container structure when
199     * tkwin is eventually deleted.
200     */
201
202    Tk_CreateEventHandler(tkwin, StructureNotifyMask, EmbeddedEventProc,
203	    (ClientData) winPtr);
204
205    /*
206     * If this is the first container, register an exit handler so that
207     * things will get cleaned up at finalization.
208     */
209
210    if (tsdPtr->firstContainerPtr == (Container *) NULL) {
211        TkCreateExitHandler(CleanupContainerList, (ClientData) NULL);
212    }
213
214    /*
215     * Save information about the container and the embedded window
216     * in a Container structure.  If there is already an existing
217     * Container structure, it means that both container and embedded
218     * app. are in the same process.
219     */
220
221    for (containerPtr = tsdPtr->firstContainerPtr;
222            containerPtr != NULL; containerPtr = containerPtr->nextPtr) {
223	if (containerPtr->parentHWnd == hwnd) {
224	    winPtr->flags |= TK_BOTH_HALVES;
225	    containerPtr->parentPtr->flags |= TK_BOTH_HALVES;
226	    break;
227	}
228    }
229    if (containerPtr == NULL) {
230	containerPtr = (Container *) ckalloc(sizeof(Container));
231	containerPtr->parentPtr = NULL;
232	containerPtr->parentHWnd = hwnd;
233	containerPtr->nextPtr = tsdPtr->firstContainerPtr;
234	tsdPtr->firstContainerPtr = containerPtr;
235    }
236
237    /*
238     * embeddedHWnd is not created yet. It will be created by TkWmMapWindow(),
239     * which will send a TK_ATTACHWINDOW to the container window.
240     * TkWinEmbeddedEventProc will process this message and set the embeddedHWnd
241     * variable
242     */
243
244    containerPtr->embeddedPtr = winPtr;
245    containerPtr->embeddedHWnd = NULL;
246
247    winPtr->flags |= TK_EMBEDDED;
248    winPtr->flags &= (~(TK_MAPPED));
249
250    return TCL_OK;
251}
252
253/*
254 *----------------------------------------------------------------------
255 *
256 * TkpMakeContainer --
257 *
258 *	This procedure is called to indicate that a particular window will
259 *	be a container for an embedded application. This changes certain
260 *	aspects of the window's behavior, such as whether it will receive
261 *	events anymore.
262 *
263 * Results:
264 *	None.
265 *
266 * Side effects:
267 *	None.
268 *
269 *----------------------------------------------------------------------
270 */
271
272void
273TkpMakeContainer(tkwin)
274    Tk_Window tkwin;
275{
276    TkWindow *winPtr = (TkWindow *) tkwin;
277    Container *containerPtr;
278    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
279            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
280
281    /*
282     * If this is the first container, register an exit handler so that
283     * things will get cleaned up at finalization.
284     */
285
286    if (tsdPtr->firstContainerPtr == (Container *) NULL) {
287        TkCreateExitHandler(CleanupContainerList, (ClientData) NULL);
288    }
289
290    /*
291     * Register the window as a container so that, for example, we can
292     * find out later if the embedded app. is in the same process.
293     */
294
295    Tk_MakeWindowExist(tkwin);
296    containerPtr = (Container *) ckalloc(sizeof(Container));
297    containerPtr->parentPtr = winPtr;
298    containerPtr->parentHWnd = Tk_GetHWND(Tk_WindowId(tkwin));
299    containerPtr->embeddedHWnd = NULL;
300    containerPtr->embeddedPtr = NULL;
301    containerPtr->nextPtr = tsdPtr->firstContainerPtr;
302    tsdPtr->firstContainerPtr = containerPtr;
303    winPtr->flags |= TK_CONTAINER;
304
305    /*
306     * Unlike in tkUnixEmbed.c, we don't make any requests for events
307     * in the embedded window here.  Now we just allow the embedding
308     * of another TK application into TK windows. When the embedded
309     * window makes a request, that will be done by sending to the
310     * container window a WM_USER message, which will be intercepted
311     * by TkWinContainerProc.
312     *
313     * We need to get structure events of the container itself, though.
314     */
315
316    Tk_CreateEventHandler(tkwin, StructureNotifyMask,
317	ContainerEventProc, (ClientData) containerPtr);
318}
319
320/*
321 *----------------------------------------------------------------------
322 *
323 * EmbeddedEventProc --
324 *
325 *	This procedure is invoked by the Tk event dispatcher when various
326 *	useful events are received for a window that is embedded in
327 *	another application.
328 *
329 * Results:
330 *	None.
331 *
332 * Side effects:
333 *	Our internal state gets cleaned up when an embedded window is
334 *	destroyed.
335 *
336 *----------------------------------------------------------------------
337 */
338
339static void
340EmbeddedEventProc(clientData, eventPtr)
341    ClientData clientData;		/* Token for container window. */
342    XEvent *eventPtr;			/* ResizeRequest event. */
343{
344    TkWindow *winPtr = (TkWindow *) clientData;
345
346    if (eventPtr->type == DestroyNotify) {
347	EmbedWindowDeleted(winPtr);
348    }
349}
350
351/*
352 *----------------------------------------------------------------------
353 *
354 * TkWinEmbeddedEventProc --
355 *
356 *	This procedure is invoked by the Tk event dispatcher when
357 *	various useful events are received for the *children* of a
358 *	container window. It forwards relevant information, such as
359 *	geometry requests, from the events into the container's
360 *	application.
361 *
362 * Results:
363 *	None.
364 *
365 * Side effects:
366 *	Depends on the event.  For example, when ConfigureRequest events
367 *	occur, geometry information gets set for the container window.
368 *
369 *----------------------------------------------------------------------
370 */
371
372LRESULT
373TkWinEmbeddedEventProc(hwnd, message, wParam, lParam)
374    HWND hwnd;
375    UINT message;
376    WPARAM wParam;
377    LPARAM lParam;
378{
379    Container *containerPtr;
380    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
381            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
382
383    /*
384     * Find the Container structure associated with the parent window.
385     */
386
387    for (containerPtr = tsdPtr->firstContainerPtr;
388	    containerPtr && containerPtr->parentHWnd != hwnd;
389	    containerPtr = containerPtr->nextPtr) {
390	/* empty loop body */
391    }
392
393    if (containerPtr == NULL) {
394	Tcl_Panic("TkWinContainerProc couldn't find Container record");
395    }
396
397    switch (message) {
398      case TK_ATTACHWINDOW:
399	/* An embedded window (either from this application or from
400	 * another application) is trying to attach to this container.
401	 * We attach it only if this container is not yet containing any
402	 * window.
403	 */
404	if (containerPtr->embeddedHWnd == NULL) {
405	    containerPtr->embeddedHWnd = (HWND)wParam;
406	} else {
407	    return 0;
408	}
409
410	break;
411      case TK_GEOMETRYREQ:
412	EmbedGeometryRequest(containerPtr, (int) wParam, lParam);
413	break;
414    }
415    return 1;
416}
417
418/*
419 *----------------------------------------------------------------------
420 *
421 * EmbedGeometryRequest --
422 *
423 *	This procedure is invoked when an embedded application requests
424 *	a particular size.  It processes the request (which may or may
425 *	not actually resize the window) and reflects the results back
426 *	to the embedded application.
427 *
428 * Results:
429 *	None.
430 *
431 * Side effects:
432 *	If we deny the child's size change request, a Configure event
433 *	is synthesized to let the child know that the size is the same
434 *	as it used to be.  Events get processed while we're waiting for
435 *	the geometry managers to do their thing.
436 *
437 *----------------------------------------------------------------------
438 */
439
440void
441EmbedGeometryRequest(containerPtr, width, height)
442    Container *containerPtr;	/* Information about the container window. */
443    int width, height;		/* Size that the child has requested. */
444{
445    TkWindow * winPtr = containerPtr->parentPtr;
446
447    /*
448     * Forward the requested size into our geometry management hierarchy
449     * via the container window.  We need to send a Configure event back
450     * to the embedded application even if we decide not to resize
451     * the window;  to make this happen, process all idle event handlers
452     * synchronously here (so that the geometry managers have had a
453     * chance to do whatever they want to do), and if the window's size
454     * didn't change then generate a configure event.
455     */
456    Tk_GeometryRequest((Tk_Window)winPtr, width, height);
457
458    if (containerPtr->embeddedHWnd != NULL) {
459	while (Tcl_DoOneEvent(TCL_IDLE_EVENTS)) {
460	    /* Empty loop body. */
461	}
462
463	SetWindowPos(containerPtr->embeddedHWnd, NULL,
464	    0, 0, winPtr->changes.width, winPtr->changes.height, SWP_NOZORDER);
465    }
466}
467
468/*
469 *----------------------------------------------------------------------
470 *
471 * ContainerEventProc --
472 *
473 *	This procedure is invoked by the Tk event dispatcher when
474 *	various useful events are received for the container window.
475 *
476 * Results:
477 *	None.
478 *
479 * Side effects:
480 *	Depends on the event.  For example, when ConfigureRequest events
481 *	occur, geometry information gets set for the container window.
482 *
483 *----------------------------------------------------------------------
484 */
485
486static void
487ContainerEventProc(clientData, eventPtr)
488    ClientData clientData;		/* Token for container window. */
489    XEvent *eventPtr;			/* ResizeRequest event. */
490{
491    Container *containerPtr = (Container *)clientData;
492    Tk_Window tkwin = (Tk_Window)containerPtr->parentPtr;
493
494    if (eventPtr->type == ConfigureNotify) {
495	if (containerPtr->embeddedPtr == NULL) {
496	    return;
497	}
498	/* Resize the embedded window, if there is any */
499	if (containerPtr->embeddedHWnd) {
500	    SetWindowPos(containerPtr->embeddedHWnd, NULL,
501	        0, 0, Tk_Width(tkwin), Tk_Height(tkwin), SWP_NOZORDER);
502	}
503    } else if (eventPtr->type == DestroyNotify) {
504	/* The container is gone, remove it from the list */
505	EmbedWindowDeleted(containerPtr->parentPtr);
506    }
507}
508
509/*
510 *----------------------------------------------------------------------
511 *
512 * TkpGetOtherWindow --
513 *
514 *	If both the container and embedded window are in the same
515 *	process, this procedure will return either one, given the other.
516 *
517 * Results:
518 *	If winPtr is a container, the return value is the token for the
519 *	embedded window, and vice versa.  If the "other" window isn't in
520 *	this process, NULL is returned.
521 *
522 * Side effects:
523 *	None.
524 *
525 *----------------------------------------------------------------------
526 */
527
528TkWindow *
529TkpGetOtherWindow(winPtr)
530    TkWindow *winPtr;		/* Tk's structure for a container or
531				 * embedded window. */
532{
533    Container *containerPtr;
534    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
535            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
536
537    for (containerPtr = tsdPtr->firstContainerPtr; containerPtr != NULL;
538	    containerPtr = containerPtr->nextPtr) {
539	if (containerPtr->embeddedPtr == winPtr) {
540	    return containerPtr->parentPtr;
541	} else if (containerPtr->parentPtr == winPtr) {
542	    return containerPtr->embeddedPtr;
543	}
544    }
545    return NULL;
546}
547
548/*
549 *----------------------------------------------------------------------
550 *
551 * TkpClaimFocus --
552 *
553 *	This procedure is invoked when someone asks or the input focus
554 *	to be put on a window in an embedded application, but the
555 *	application doesn't currently have the focus.  It requests the
556 *	input focus from the container application.
557 *
558 * Results:
559 *	None.
560 *
561 * Side effects:
562 *	The input focus may change.
563 *
564 *----------------------------------------------------------------------
565 */
566
567void
568TkpClaimFocus(topLevelPtr, force)
569    TkWindow *topLevelPtr;		/* Top-level window containing desired
570					 * focus window; should be embedded. */
571    int force;				/* One means that the container should
572					 * claim the focus if it doesn't
573					 * currently have it. */
574{
575    HWND hwnd = GetParent(Tk_GetHWND(topLevelPtr->window));
576    SendMessage(hwnd, TK_CLAIMFOCUS, (WPARAM) force, 0);
577}
578
579/*
580 *----------------------------------------------------------------------
581 *
582 * TkpRedirectKeyEvent --
583 *
584 *	This procedure is invoked when a key press or release event
585 *	arrives for an application that does not believe it owns the
586 *	input focus.  This can happen because of embedding; for example,
587 *	X can send an event to an embedded application when the real
588 *	focus window is in the container application and is an ancestor
589 *	of the container.  This procedure's job is to forward the event
590 *	back to the application where it really belongs.
591 *
592 * Results:
593 *	None.
594 *
595 * Side effects:
596 *	The event may get sent to a different application.
597 *
598 *----------------------------------------------------------------------
599 */
600
601void
602TkpRedirectKeyEvent(winPtr, eventPtr)
603    TkWindow *winPtr;		/* Window to which the event was originally
604				 * reported. */
605    XEvent *eventPtr;		/* X event to redirect (should be KeyPress
606				 * or KeyRelease). */
607{
608    /* not implemented */
609}
610
611/*
612 *----------------------------------------------------------------------
613 *
614 * EmbedWindowDeleted --
615 *
616 *	This procedure is invoked when a window involved in embedding
617 *	(as either the container or the embedded application) is
618 *	destroyed.  It cleans up the Container structure for the window.
619 *
620 * Results:
621 *	None.
622 *
623 * Side effects:
624 *	A Container structure may be freed.
625 *
626 *----------------------------------------------------------------------
627 */
628
629static void
630EmbedWindowDeleted(winPtr)
631    TkWindow *winPtr;		/* Tk's information about window that
632				 * was deleted. */
633{
634    Container *containerPtr, *prevPtr;
635    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
636            Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
637
638    /*
639     * Find the Container structure for this window work.  Delete the
640     * information about the embedded application and free the container's
641     * record.
642     * The main container may be null. [Bug #476176]
643     */
644
645    prevPtr = NULL;
646    containerPtr = tsdPtr->firstContainerPtr;
647    if (containerPtr == NULL) return;
648    while (1) {
649	if (containerPtr->embeddedPtr == winPtr) {
650	    containerPtr->embeddedHWnd = NULL;
651	    containerPtr->embeddedPtr = NULL;
652	    break;
653	}
654	if (containerPtr->parentPtr == winPtr) {
655	    containerPtr->parentPtr = NULL;
656	    break;
657	}
658	prevPtr = containerPtr;
659	containerPtr = containerPtr->nextPtr;
660	if (containerPtr == NULL) {
661	    panic("EmbedWindowDeleted couldn't find window");
662	}
663    }
664    if ((containerPtr->embeddedPtr == NULL)
665	    && (containerPtr->parentPtr == NULL)) {
666	if (prevPtr == NULL) {
667	    tsdPtr->firstContainerPtr = containerPtr->nextPtr;
668	} else {
669	    prevPtr->nextPtr = containerPtr->nextPtr;
670	}
671	ckfree((char *) containerPtr);
672    }
673}
674