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