1/* 2 * tkClipboard.c -- 3 * 4 * This file manages the clipboard for the Tk toolkit, 5 * maintaining a collection of data buffers that will be 6 * supplied on demand to requesting applications. 7 * 8 * Copyright (c) 1994 The Regents of the University of California. 9 * Copyright (c) 1994-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: tkClipboard.c,v 1.12.2.1 2004/05/03 22:23:08 hobbs Exp $ 15 */ 16 17#include "tkInt.h" 18#include "tkPort.h" 19#include "tkSelect.h" 20 21/* 22 * Prototypes for procedures used only in this file: 23 */ 24 25static int ClipboardAppHandler _ANSI_ARGS_((ClientData clientData, 26 int offset, char *buffer, int maxBytes)); 27static int ClipboardHandler _ANSI_ARGS_((ClientData clientData, 28 int offset, char *buffer, int maxBytes)); 29static int ClipboardWindowHandler _ANSI_ARGS_(( 30 ClientData clientData, int offset, char *buffer, 31 int maxBytes)); 32static void ClipboardLostSel _ANSI_ARGS_((ClientData clientData)); 33static int ClipboardGetProc _ANSI_ARGS_((ClientData clientData, 34 Tcl_Interp *interp, char *portion)); 35 36/* 37 *---------------------------------------------------------------------- 38 * 39 * ClipboardHandler -- 40 * 41 * This procedure acts as selection handler for the 42 * clipboard manager. It extracts the required chunk of 43 * data from the buffer chain for a given selection target. 44 * 45 * Results: 46 * The return value is a count of the number of bytes 47 * actually stored at buffer. 48 * 49 * Side effects: 50 * None. 51 * 52 *---------------------------------------------------------------------- 53 */ 54 55static int 56ClipboardHandler(clientData, offset, buffer, maxBytes) 57 ClientData clientData; /* Information about data to fetch. */ 58 int offset; /* Return selection bytes starting at this 59 * offset. */ 60 char *buffer; /* Place to store converted selection. */ 61 int maxBytes; /* Maximum # of bytes to store at buffer. */ 62{ 63 TkClipboardTarget *targetPtr = (TkClipboardTarget*) clientData; 64 TkClipboardBuffer *cbPtr; 65 char *srcPtr, *destPtr; 66 int count = 0; 67 int scanned = 0; 68 size_t length, freeCount; 69 70 /* 71 * Skip to buffer containing offset byte 72 */ 73 74 for (cbPtr = targetPtr->firstBufferPtr; ; cbPtr = cbPtr->nextPtr) { 75 if (cbPtr == NULL) { 76 return 0; 77 } 78 if (scanned + cbPtr->length > offset) { 79 break; 80 } 81 scanned += cbPtr->length; 82 } 83 84 /* 85 * Copy up to maxBytes or end of list, switching buffers as needed. 86 */ 87 88 freeCount = maxBytes; 89 srcPtr = cbPtr->buffer + (offset - scanned); 90 destPtr = buffer; 91 length = cbPtr->length - (offset - scanned); 92 while (1) { 93 if (length > freeCount) { 94 strncpy(destPtr, srcPtr, freeCount); 95 return maxBytes; 96 } else { 97 strncpy(destPtr, srcPtr, length); 98 destPtr += length; 99 count += length; 100 freeCount -= length; 101 } 102 cbPtr = cbPtr->nextPtr; 103 if (cbPtr == NULL) { 104 break; 105 } 106 srcPtr = cbPtr->buffer; 107 length = cbPtr->length; 108 } 109 return count; 110} 111 112/* 113 *---------------------------------------------------------------------- 114 * 115 * ClipboardAppHandler -- 116 * 117 * This procedure acts as selection handler for retrievals of type 118 * TK_APPLICATION. It returns the name of the application that 119 * owns the clipboard. Note: we can't use the default Tk 120 * selection handler for this selection type, because the clipboard 121 * window isn't a "real" window and doesn't have the necessary 122 * information. 123 * 124 * Results: 125 * The return value is a count of the number of bytes 126 * actually stored at buffer. 127 * 128 * Side effects: 129 * None. 130 * 131 *---------------------------------------------------------------------- 132 */ 133 134static int 135ClipboardAppHandler(clientData, offset, buffer, maxBytes) 136 ClientData clientData; /* Pointer to TkDisplay structure. */ 137 int offset; /* Return selection bytes starting at this 138 * offset. */ 139 char *buffer; /* Place to store converted selection. */ 140 int maxBytes; /* Maximum # of bytes to store at buffer. */ 141{ 142 TkDisplay *dispPtr = (TkDisplay *) clientData; 143 size_t length; 144 CONST char *p; 145 146 p = dispPtr->clipboardAppPtr->winPtr->nameUid; 147 length = strlen(p); 148 length -= offset; 149 if (length <= 0) { 150 return 0; 151 } 152 if (length > (size_t) maxBytes) { 153 length = maxBytes; 154 } 155 strncpy(buffer, p, length); 156 return length; 157} 158 159/* 160 *---------------------------------------------------------------------- 161 * 162 * ClipboardWindowHandler -- 163 * 164 * This procedure acts as selection handler for retrievals of 165 * type TK_WINDOW. Since the clipboard doesn't correspond to 166 * any particular window, we just return ".". We can't use Tk's 167 * default handler for this selection type, because the clipboard 168 * window isn't a valid window. 169 * 170 * Results: 171 * The return value is 1, the number of non-null bytes stored 172 * at buffer. 173 * 174 * Side effects: 175 * None. 176 * 177 *---------------------------------------------------------------------- 178 */ 179 180static int 181ClipboardWindowHandler(clientData, offset, buffer, maxBytes) 182 ClientData clientData; /* Not used. */ 183 int offset; /* Return selection bytes starting at this 184 * offset. */ 185 char *buffer; /* Place to store converted selection. */ 186 int maxBytes; /* Maximum # of bytes to store at buffer. */ 187{ 188 buffer[0] = '.'; 189 buffer[1] = 0; 190 return 1; 191} 192 193/* 194 *---------------------------------------------------------------------- 195 * 196 * ClipboardLostSel -- 197 * 198 * This procedure is invoked whenever clipboard ownership is 199 * claimed by another window. It just sets a flag so that we 200 * know the clipboard was taken away. 201 * 202 * Results: 203 * None. 204 * 205 * Side effects: 206 * The clipboard is marked as inactive. 207 * 208 *---------------------------------------------------------------------- 209 */ 210 211static void 212ClipboardLostSel(clientData) 213 ClientData clientData; /* Pointer to TkDisplay structure. */ 214{ 215 TkDisplay *dispPtr = (TkDisplay*) clientData; 216 217 dispPtr->clipboardActive = 0; 218} 219 220/* 221 *---------------------------------------------------------------------- 222 * 223 * Tk_ClipboardClear -- 224 * 225 * Take control of the clipboard and clear out the previous 226 * contents. This procedure must be invoked before any 227 * calls to Tk_ClipboardAppend. 228 * 229 * Results: 230 * A standard Tcl result. If an error occurs, an error message is 231 * left in the interp's result. 232 * 233 * Side effects: 234 * From now on, requests for the CLIPBOARD selection will be 235 * directed to the clipboard manager routines associated with 236 * clipWindow for the display of tkwin. In order to guarantee 237 * atomicity, no event handling should occur between 238 * Tk_ClipboardClear and the following Tk_ClipboardAppend 239 * calls. This procedure may cause a user-defined LostSel command 240 * to be invoked when the CLIPBOARD is claimed, so any calling 241 * function should be reentrant at the point Tk_ClipboardClear is 242 * invoked. 243 * 244 *---------------------------------------------------------------------- 245 */ 246 247int 248Tk_ClipboardClear(interp, tkwin) 249 Tcl_Interp *interp; /* Interpreter to use for error reporting. */ 250 Tk_Window tkwin; /* Window in application that is clearing 251 * clipboard; identifies application and 252 * display. */ 253{ 254 TkWindow *winPtr = (TkWindow *) tkwin; 255 TkDisplay *dispPtr = winPtr->dispPtr; 256 TkClipboardTarget *targetPtr, *nextTargetPtr; 257 TkClipboardBuffer *cbPtr, *nextCbPtr; 258 259 if (dispPtr->clipWindow == NULL) { 260 int result; 261 262 result = TkClipInit(interp, dispPtr); 263 if (result != TCL_OK) { 264 return result; 265 } 266 } 267 268 /* 269 * Discard any existing clipboard data and delete the selection 270 * handler(s) associated with that data. 271 */ 272 273 for (targetPtr = dispPtr->clipTargetPtr; targetPtr != NULL; 274 targetPtr = nextTargetPtr) { 275 for (cbPtr = targetPtr->firstBufferPtr; cbPtr != NULL; 276 cbPtr = nextCbPtr) { 277 ckfree(cbPtr->buffer); 278 nextCbPtr = cbPtr->nextPtr; 279 ckfree((char *) cbPtr); 280 } 281 nextTargetPtr = targetPtr->nextPtr; 282 Tk_DeleteSelHandler(dispPtr->clipWindow, dispPtr->clipboardAtom, 283 targetPtr->type); 284 ckfree((char *) targetPtr); 285 } 286 dispPtr->clipTargetPtr = NULL; 287 288 /* 289 * Reclaim the clipboard selection if we lost it. 290 */ 291 292 if (!dispPtr->clipboardActive) { 293 Tk_OwnSelection(dispPtr->clipWindow, dispPtr->clipboardAtom, 294 ClipboardLostSel, (ClientData) dispPtr); 295 dispPtr->clipboardActive = 1; 296 } 297 dispPtr->clipboardAppPtr = winPtr->mainPtr; 298 return TCL_OK; 299} 300 301/* 302 *---------------------------------------------------------------------- 303 * 304 * Tk_ClipboardAppend -- 305 * 306 * Append a buffer of data to the clipboard. The first buffer of 307 * a given type determines the format for that type. Any successive 308 * appends to that type must have the same format or an error will 309 * be returned. Tk_ClipboardClear must be called before a sequence 310 * of Tk_ClipboardAppend calls can be issued. In order to guarantee 311 * atomicity, no event handling should occur between Tk_ClipboardClear 312 * and the following Tk_ClipboardAppend calls. 313 * 314 * Results: 315 * A standard Tcl result. If an error is returned, an error message 316 * is left in the interp's result. 317 * 318 * Side effects: 319 * The specified buffer will be copied onto the end of the clipboard. 320 * The clipboard maintains a list of buffers which will be used to 321 * supply the data for a selection get request. The first time a given 322 * type is appended, Tk_ClipboardAppend will register a selection 323 * handler of the appropriate type. 324 * 325 *---------------------------------------------------------------------- 326 */ 327 328int 329Tk_ClipboardAppend(interp, tkwin, type, format, buffer) 330 Tcl_Interp *interp; /* Used for error reporting. */ 331 Tk_Window tkwin; /* Window that selects a display. */ 332 Atom type; /* The desired conversion type for this 333 * clipboard item, e.g. STRING or LENGTH. */ 334 Atom format; /* Format in which the selection 335 * information should be returned to 336 * the requestor. */ 337 char* buffer; /* NULL terminated string containing the data 338 * to be added to the clipboard. */ 339{ 340 TkWindow *winPtr = (TkWindow *) tkwin; 341 TkDisplay *dispPtr = winPtr->dispPtr; 342 TkClipboardTarget *targetPtr; 343 TkClipboardBuffer *cbPtr; 344 345 /* 346 * If this application doesn't already own the clipboard, clear 347 * the clipboard. If we don't own the clipboard selection, claim it. 348 */ 349 350 if (dispPtr->clipboardAppPtr != winPtr->mainPtr) { 351 Tk_ClipboardClear(interp, tkwin); 352 } else if (!dispPtr->clipboardActive) { 353 Tk_OwnSelection(dispPtr->clipWindow, dispPtr->clipboardAtom, 354 ClipboardLostSel, (ClientData) dispPtr); 355 dispPtr->clipboardActive = 1; 356 } 357 358 /* 359 * Check to see if the specified target is already present on the 360 * clipboard. If it isn't, we need to create a new target; otherwise, 361 * we just append the new buffer to the clipboard list. 362 */ 363 364 for (targetPtr = dispPtr->clipTargetPtr; targetPtr != NULL; 365 targetPtr = targetPtr->nextPtr) { 366 if (targetPtr->type == type) 367 break; 368 } 369 if (targetPtr == NULL) { 370 targetPtr = (TkClipboardTarget*) ckalloc(sizeof(TkClipboardTarget)); 371 targetPtr->type = type; 372 targetPtr->format = format; 373 targetPtr->firstBufferPtr = targetPtr->lastBufferPtr = NULL; 374 targetPtr->nextPtr = dispPtr->clipTargetPtr; 375 dispPtr->clipTargetPtr = targetPtr; 376 Tk_CreateSelHandler(dispPtr->clipWindow, dispPtr->clipboardAtom, 377 type, ClipboardHandler, (ClientData) targetPtr, format); 378 } else if (targetPtr->format != format) { 379 Tcl_AppendResult(interp, "format \"", Tk_GetAtomName(tkwin, format), 380 "\" does not match current format \"", 381 Tk_GetAtomName(tkwin, targetPtr->format),"\" for ", 382 Tk_GetAtomName(tkwin, type), (char *) NULL); 383 return TCL_ERROR; 384 } 385 386 /* 387 * Append a new buffer to the buffer chain. 388 */ 389 390 cbPtr = (TkClipboardBuffer*) ckalloc(sizeof(TkClipboardBuffer)); 391 cbPtr->nextPtr = NULL; 392 if (targetPtr->lastBufferPtr != NULL) { 393 targetPtr->lastBufferPtr->nextPtr = cbPtr; 394 } else { 395 targetPtr->firstBufferPtr = cbPtr; 396 } 397 targetPtr->lastBufferPtr = cbPtr; 398 399 cbPtr->length = strlen(buffer); 400 cbPtr->buffer = (char *) ckalloc((unsigned) (cbPtr->length + 1)); 401 strcpy(cbPtr->buffer, buffer); 402 403 TkSelUpdateClipboard((TkWindow*)(dispPtr->clipWindow), targetPtr); 404 405 return TCL_OK; 406} 407 408/* 409 *---------------------------------------------------------------------- 410 * 411 * Tk_ClipboardObjCmd -- 412 * 413 * This procedure is invoked to process the "clipboard" Tcl 414 * command. See the user documentation for details on what 415 * it does. 416 * 417 * Results: 418 * A standard Tcl result. 419 * 420 * Side effects: 421 * See the user documentation. 422 * 423 *---------------------------------------------------------------------- 424 */ 425 426int 427Tk_ClipboardObjCmd(clientData, interp, objc, objv) 428 ClientData clientData; /* Main window associated with 429 * interpreter. */ 430 Tcl_Interp *interp; /* Current interpreter. */ 431 int objc; /* Number of arguments. */ 432 Tcl_Obj *CONST objv[]; /* Argument strings. */ 433{ 434 Tk_Window tkwin = (Tk_Window) clientData; 435 char *path = NULL; 436 Atom selection; 437 static CONST char *optionStrings[] = { "append", "clear", "get", NULL }; 438 enum options { CLIPBOARD_APPEND, CLIPBOARD_CLEAR, CLIPBOARD_GET }; 439 int index, i; 440 441 if (objc < 2) { 442 Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); 443 return TCL_ERROR; 444 } 445 446 if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, 447 &index) != TCL_OK) { 448 return TCL_ERROR; 449 } 450 451 switch ((enum options) index) { 452 case CLIPBOARD_APPEND: { 453 Atom target, format; 454 char *targetName = NULL; 455 char *formatName = NULL; 456 char *string; 457 static CONST char *appendOptionStrings[] = { 458 "-displayof", "-format", "-type", NULL 459 }; 460 enum appendOptions { APPEND_DISPLAYOF, APPEND_FORMAT, 461 APPEND_TYPE }; 462 int subIndex, length; 463 464 for (i = 2; i < objc - 1; i++) { 465 string = Tcl_GetStringFromObj(objv[i], &length); 466 if (string[0] != '-') { 467 break; 468 } 469 470 /* 471 * If the argument is "--", it signifies the end of arguments. 472 */ 473 if (string[1] == '-' && length == 2) { 474 i++; 475 break; 476 } 477 if (Tcl_GetIndexFromObj(interp, objv[i], appendOptionStrings, 478 "option", 0, &subIndex) != TCL_OK) { 479 return TCL_ERROR; 480 } 481 482 /* 483 * Increment i so that it points to the value for the flag 484 * instead of the flag itself. 485 */ 486 487 i++; 488 if (i >= objc) { 489 Tcl_AppendResult(interp, "value for \"", string, 490 "\" missing", (char *) NULL); 491 return TCL_ERROR; 492 } 493 switch ((enum appendOptions) subIndex) { 494 case APPEND_DISPLAYOF: 495 path = Tcl_GetString(objv[i]); 496 break; 497 case APPEND_FORMAT: 498 formatName = Tcl_GetString(objv[i]); 499 break; 500 case APPEND_TYPE: 501 targetName = Tcl_GetString(objv[i]); 502 break; 503 } 504 } 505 if (objc - i != 1) { 506 Tcl_WrongNumArgs(interp, 2, objv, "?options? data"); 507 return TCL_ERROR; 508 } 509 if (path != NULL) { 510 tkwin = Tk_NameToWindow(interp, path, tkwin); 511 } 512 if (tkwin == NULL) { 513 return TCL_ERROR; 514 } 515 if (targetName != NULL) { 516 target = Tk_InternAtom(tkwin, targetName); 517 } else { 518 target = XA_STRING; 519 } 520 if (formatName != NULL) { 521 format = Tk_InternAtom(tkwin, formatName); 522 } else { 523 format = XA_STRING; 524 } 525 return Tk_ClipboardAppend(interp, tkwin, target, format, 526 Tcl_GetString(objv[i])); 527 } 528 case CLIPBOARD_CLEAR: { 529 static CONST char *clearOptionStrings[] = { "-displayof", NULL }; 530 enum clearOptions { CLEAR_DISPLAYOF }; 531 int subIndex; 532 if (objc != 2 && objc != 4) { 533 Tcl_WrongNumArgs(interp, 2, objv, "?-displayof window?"); 534 return TCL_ERROR; 535 } 536 537 if (objc == 4) { 538 if (Tcl_GetIndexFromObj(interp, objv[2], clearOptionStrings, 539 "option", 0, &subIndex) != TCL_OK) { 540 return TCL_ERROR; 541 } 542 if ((enum clearOptions) subIndex == CLEAR_DISPLAYOF) { 543 path = Tcl_GetString(objv[3]); 544 } 545 } 546 if (path != NULL) { 547 tkwin = Tk_NameToWindow(interp, path, tkwin); 548 } 549 if (tkwin == NULL) { 550 return TCL_ERROR; 551 } 552 return Tk_ClipboardClear(interp, tkwin); 553 } 554 case CLIPBOARD_GET: { 555 Atom target; 556 char *targetName = NULL; 557 Tcl_DString selBytes; 558 int result; 559 char *string; 560 static CONST char *getOptionStrings[] = { 561 "-displayof", "-type", NULL 562 }; 563 enum getOptions { APPEND_DISPLAYOF, APPEND_TYPE }; 564 int subIndex; 565 566 for (i = 2; i < objc; i++) { 567 string = Tcl_GetString(objv[i]); 568 if (string[0] != '-') { 569 break; 570 } 571 if (Tcl_GetIndexFromObj(interp, objv[i], getOptionStrings, 572 "option", 0, &subIndex) != TCL_OK) { 573 return TCL_ERROR; 574 } 575 i++; 576 if (i >= objc) { 577 Tcl_AppendResult(interp, "value for \"", string, 578 "\" missing", (char *) NULL); 579 return TCL_ERROR; 580 } 581 switch ((enum getOptions) subIndex) { 582 case APPEND_DISPLAYOF: 583 path = Tcl_GetString(objv[i]); 584 break; 585 case APPEND_TYPE: 586 targetName = Tcl_GetString(objv[i]); 587 break; 588 } 589 } 590 if (path != NULL) { 591 tkwin = Tk_NameToWindow(interp, path, tkwin); 592 } 593 if (tkwin == NULL) { 594 return TCL_ERROR; 595 } 596 selection = Tk_InternAtom(tkwin, "CLIPBOARD"); 597 598 if (objc - i > 1) { 599 Tcl_WrongNumArgs(interp, 2, objv, "?options?"); 600 return TCL_ERROR; 601 } else if (objc - i == 1) { 602 target = Tk_InternAtom(tkwin, Tcl_GetString(objv[i])); 603 } else if (targetName != NULL) { 604 target = Tk_InternAtom(tkwin, targetName); 605 } else { 606 target = XA_STRING; 607 } 608 609 Tcl_DStringInit(&selBytes); 610 result = Tk_GetSelection(interp, tkwin, selection, target, 611 ClipboardGetProc, (ClientData) &selBytes); 612 if (result == TCL_OK) { 613 Tcl_DStringResult(interp, &selBytes); 614 } else { 615 Tcl_DStringFree(&selBytes); 616 } 617 return result; 618 } 619 } 620 return TCL_OK; 621} 622 623/* 624 *---------------------------------------------------------------------- 625 * 626 * TkClipInit -- 627 * 628 * This procedure is called to initialize the window for claiming 629 * clipboard ownership and for receiving selection get results. This 630 * function is called from tkSelect.c as well as tkClipboard.c. 631 * 632 * Results: 633 * The result is a standard Tcl return value, which is normally TCL_OK. 634 * If an error occurs then an error message is left in the interp's 635 * result and TCL_ERROR is returned. 636 * 637 * Side effects: 638 * Sets up the clipWindow and related data structures. 639 * 640 *---------------------------------------------------------------------- 641 */ 642 643int 644TkClipInit(interp, dispPtr) 645 Tcl_Interp *interp; /* Interpreter to use for error 646 * reporting. */ 647 register TkDisplay *dispPtr;/* Display to initialize. */ 648{ 649 XSetWindowAttributes atts; 650 651 dispPtr->clipTargetPtr = NULL; 652 dispPtr->clipboardActive = 0; 653 dispPtr->clipboardAppPtr = NULL; 654 655 /* 656 * Create the window used for clipboard ownership and selection retrieval, 657 * and set up an event handler for it. 658 */ 659 660 dispPtr->clipWindow = Tk_CreateWindow(interp, (Tk_Window) NULL, 661 "_clip", DisplayString(dispPtr->display)); 662 if (dispPtr->clipWindow == NULL) { 663 return TCL_ERROR; 664 } 665 Tcl_Preserve((ClientData) dispPtr->clipWindow); 666 atts.override_redirect = True; 667 Tk_ChangeWindowAttributes(dispPtr->clipWindow, CWOverrideRedirect, &atts); 668 Tk_MakeWindowExist(dispPtr->clipWindow); 669 670 if (dispPtr->multipleAtom == None) { 671 /* 672 * Need to invoke selection initialization to make sure that 673 * atoms we depend on below are defined. 674 */ 675 676 TkSelInit(dispPtr->clipWindow); 677 } 678 679 /* 680 * Create selection handlers for types TK_APPLICATION and TK_WINDOW 681 * on this window. Can't use the default handlers for these types 682 * because this isn't a full-fledged window. 683 */ 684 685 Tk_CreateSelHandler(dispPtr->clipWindow, dispPtr->clipboardAtom, 686 dispPtr->applicationAtom, ClipboardAppHandler, 687 (ClientData) dispPtr, XA_STRING); 688 Tk_CreateSelHandler(dispPtr->clipWindow, dispPtr->clipboardAtom, 689 dispPtr->windowAtom, ClipboardWindowHandler, 690 (ClientData) dispPtr, XA_STRING); 691 return TCL_OK; 692} 693 694/* 695 *-------------------------------------------------------------- 696 * 697 * ClipboardGetProc -- 698 * 699 * This procedure is invoked to process pieces of the selection 700 * as they arrive during "clipboard get" commands. 701 * 702 * Results: 703 * Always returns TCL_OK. 704 * 705 * Side effects: 706 * Bytes get appended to the dynamic string pointed to by the 707 * clientData argument. 708 * 709 *-------------------------------------------------------------- 710 */ 711 712 /* ARGSUSED */ 713static int 714ClipboardGetProc(clientData, interp, portion) 715 ClientData clientData; /* Dynamic string holding partially 716 * assembled selection. */ 717 Tcl_Interp *interp; /* Interpreter used for error 718 * reporting (not used). */ 719 char *portion; /* New information to be appended. */ 720{ 721 Tcl_DStringAppend((Tcl_DString *) clientData, portion, -1); 722 return TCL_OK; 723} 724 725