/*1* tkUnixSelect.c --2*3* This file contains X specific routines for manipulating4* selections.5*6* Copyright (c) 1995 Sun Microsystems, Inc.7*8* See the file "license.terms" for information on usage and redistribution9* of this file, and for a DISCLAIMER OF ALL WARRANTIES.10*11* SCCS: @(#) tkUnixSelect.c 1.5 96/03/29 14:14:3112*/1314#include "tkInt.h"15#include "tkSelect.h"1617/*18* When handling INCR-style selection retrievals, the selection owner19* uses the following data structure to communicate between the20* ConvertSelection procedure and TkSelPropProc.21*/2223typedef struct IncrInfo {24TkWindow *winPtr; /* Window that owns selection. */25Atom selection; /* Selection that is being retrieved. */26Atom *multAtoms; /* Information about conversions to27* perform: one or more pairs of28* (target, property). This either29* points to a retrieved property (for30* MULTIPLE retrievals) or to a static31* array. */32unsigned long numConversions;33/* Number of entries in offsets (same as34* # of pairs in multAtoms). */35int *offsets; /* One entry for each pair in36* multAtoms; -1 means all data has37* been transferred for this38* conversion. -2 means only the39* final zero-length transfer still40* has to be done. Otherwise it is the41* offset of the next chunk of data42* to transfer. This array is malloc-ed. */43int numIncrs; /* Number of entries in offsets that44* aren't -1 (i.e. # of INCR-mode transfers45* not yet completed). */46Tcl_TimerToken timeout; /* Token for timer procedure. */47int idleTime; /* Number of seconds since we heard48* anything from the selection49* requestor. */50Window reqWindow; /* Requestor's window id. */51Time time; /* Timestamp corresponding to52* selection at beginning of request;53* used to abort transfer if selection54* changes. */55struct IncrInfo *nextPtr; /* Next in list of all INCR-style56* retrievals currently pending. */57} IncrInfo;5859static IncrInfo *pendingIncrs = NULL;60/* List of all incr structures61* currently active. */6263/*64* Largest property that we'll accept when sending or receiving the65* selection:66*/6768#define MAX_PROP_WORDS 1000006970static TkSelRetrievalInfo *pendingRetrievals = NULL;71/* List of all retrievals currently72* being waited for. */7374/*75* Forward declarations for procedures defined in this file:76*/7778static void ConvertSelection _ANSI_ARGS_((TkWindow *winPtr,79XSelectionRequestEvent *eventPtr));80static void IncrTimeoutProc _ANSI_ARGS_((ClientData clientData));81static char * SelCvtFromX _ANSI_ARGS_((long *propPtr, int numValues,82Atom type, Tk_Window tkwin));83static long * SelCvtToX _ANSI_ARGS_((char *string, Atom type,84Tk_Window tkwin, int *numLongsPtr));85static int SelectionSize _ANSI_ARGS_((TkSelHandler *selPtr));86static void SelRcvIncrProc _ANSI_ARGS_((ClientData clientData,87XEvent *eventPtr));88static void SelTimeoutProc _ANSI_ARGS_((ClientData clientData));8990/*91*----------------------------------------------------------------------92*93* TkSelGetSelection --94*95* Retrieve the specified selection from another process.96*97* Results:98* The return value is a standard Tcl return value.99* If an error occurs (such as no selection exists)100* then an error message is left in interp->result.101*102* Side effects:103* None.104*105*----------------------------------------------------------------------106*/107108int109TkSelGetSelection(interp, tkwin, selection, target, proc, clientData)110Tcl_Interp *interp; /* Interpreter to use for reporting111* errors. */112Tk_Window tkwin; /* Window on whose behalf to retrieve113* the selection (determines display114* from which to retrieve). */115Atom selection; /* Selection to retrieve. */116Atom target; /* Desired form in which selection117* is to be returned. */118Tk_GetSelProc *proc; /* Procedure to call to process the119* selection, once it has been retrieved. */120ClientData clientData; /* Arbitrary value to pass to proc. */121{122TkSelRetrievalInfo retr;123TkWindow *winPtr = (TkWindow *) tkwin;124TkDisplay *dispPtr = winPtr->dispPtr;125126/*127* The selection is owned by some other process. To128* retrieve it, first record information about the retrieval129* in progress. Use an internal window as the requestor.130*/131132retr.interp = interp;133if (dispPtr->clipWindow == NULL) {134int result;135136result = TkClipInit(interp, dispPtr);137if (result != TCL_OK) {138return result;139}140}141retr.winPtr = (TkWindow *) dispPtr->clipWindow;142retr.selection = selection;143retr.property = selection;144retr.target = target;145retr.proc = proc;146retr.clientData = clientData;147retr.result = -1;148retr.idleTime = 0;149retr.nextPtr = pendingRetrievals;150pendingRetrievals = &retr;151152/*153* Initiate the request for the selection. Note: can't use154* TkCurrentTime for the time. If we do, and this application hasn't155* received any X events in a long time, the current time will be way156* in the past and could even predate the time when the selection was157* made; if this happens, the request will be rejected.158*/159160XConvertSelection(winPtr->display, retr.selection, retr.target,161retr.property, retr.winPtr->window, CurrentTime);162163/*164* Enter a loop processing X events until the selection165* has been retrieved and processed. If no response is166* received within a few seconds, then timeout.167*/168169retr.timeout = Tcl_CreateTimerHandler(1000, SelTimeoutProc,170(ClientData) &retr);171while (retr.result == -1) {172Tcl_DoOneEvent(0);173}174Tcl_DeleteTimerHandler(retr.timeout);175176/*177* Unregister the information about the selection retrieval178* in progress.179*/180181if (pendingRetrievals == &retr) {182pendingRetrievals = retr.nextPtr;183} else {184TkSelRetrievalInfo *retrPtr;185186for (retrPtr = pendingRetrievals; retrPtr != NULL;187retrPtr = retrPtr->nextPtr) {188if (retrPtr->nextPtr == &retr) {189retrPtr->nextPtr = retr.nextPtr;190break;191}192}193}194return retr.result;195}196197/*198*----------------------------------------------------------------------199*200* TkSelPropProc --201*202* This procedure is invoked when property-change events203* occur on windows not known to the toolkit. Its function204* is to implement the sending side of the INCR selection205* retrieval protocol when the selection requestor deletes206* the property containing a part of the selection.207*208* Results:209* None.210*211* Side effects:212* If the property that is receiving the selection was just213* deleted, then a new piece of the selection is fetched and214* placed in the property, until eventually there's no more215* selection to fetch.216*217*----------------------------------------------------------------------218*/219220void221TkSelPropProc(eventPtr)222register XEvent *eventPtr; /* X PropertyChange event. */223{224register IncrInfo *incrPtr;225int i, format;226Atom target, formatType;227register TkSelHandler *selPtr;228long buffer[TK_SEL_WORDS_AT_ONCE];229int numItems;230char *propPtr;231Tk_ErrorHandler errorHandler;232233/*234* See if this event announces the deletion of a property being235* used for an INCR transfer. If so, then add the next chunk of236* data to the property.237*/238239if (eventPtr->xproperty.state != PropertyDelete) {240return;241}242for (incrPtr = pendingIncrs; incrPtr != NULL;243incrPtr = incrPtr->nextPtr) {244if (incrPtr->reqWindow != eventPtr->xproperty.window) {245continue;246}247for (i = 0; i < incrPtr->numConversions; i++) {248if ((eventPtr->xproperty.atom != incrPtr->multAtoms[2*i + 1])249|| (incrPtr->offsets[i] == -1)){250continue;251}252target = incrPtr->multAtoms[2*i];253incrPtr->idleTime = 0;254for (selPtr = incrPtr->winPtr->selHandlerList; ;255selPtr = selPtr->nextPtr) {256if (selPtr == NULL) {257incrPtr->multAtoms[2*i + 1] = None;258incrPtr->offsets[i] = -1;259incrPtr->numIncrs --;260return;261}262if ((selPtr->target == target)263&& (selPtr->selection == incrPtr->selection)) {264formatType = selPtr->format;265if (incrPtr->offsets[i] == -2) {266numItems = 0;267((char *) buffer)[0] = 0;268} else {269TkSelInProgress ip;270ip.selPtr = selPtr;271ip.nextPtr = pendingPtr;272pendingPtr = &ip;273numItems = (*selPtr->proc)(selPtr->clientData,274incrPtr->offsets[i], (char *) buffer,275TK_SEL_BYTES_AT_ONCE);276pendingPtr = ip.nextPtr;277if (ip.selPtr == NULL) {278/*279* The selection handler deleted itself.280*/281282return;283}284if (numItems > TK_SEL_BYTES_AT_ONCE) {285panic("selection handler returned too many bytes");286} else {287if (numItems < 0) {288numItems = 0;289}290}291((char *) buffer)[numItems] = '\0';292}293if (numItems < TK_SEL_BYTES_AT_ONCE) {294if (numItems <= 0) {295incrPtr->offsets[i] = -1;296incrPtr->numIncrs--;297} else {298incrPtr->offsets[i] = -2;299}300} else {301incrPtr->offsets[i] += numItems;302}303if (formatType == XA_STRING) {304propPtr = (char *) buffer;305format = 8;306} else {307propPtr = (char *) SelCvtToX((char *) buffer,308formatType, (Tk_Window) incrPtr->winPtr,309&numItems);310format = 32;311}312errorHandler = Tk_CreateErrorHandler(313eventPtr->xproperty.display, -1, -1, -1,314(int (*)()) NULL, (ClientData) NULL);315XChangeProperty(eventPtr->xproperty.display,316eventPtr->xproperty.window,317eventPtr->xproperty.atom, formatType,318format, PropModeReplace,319(unsigned char *) propPtr, numItems);320Tk_DeleteErrorHandler(errorHandler);321if (propPtr != (char *) buffer) {322ckfree(propPtr);323}324return;325}326}327}328}329}330331/*332*--------------------------------------------------------------333*334* TkSelEventProc --335*336* This procedure is invoked whenever a selection-related337* event occurs. It does the lion's share of the work338* in implementing the selection protocol.339*340* Results:341* None.342*343* Side effects:344* Lots: depends on the type of event.345*346*--------------------------------------------------------------347*/348349void350TkSelEventProc(tkwin, eventPtr)351Tk_Window tkwin; /* Window for which event was352* targeted. */353register XEvent *eventPtr; /* X event: either SelectionClear,354* SelectionRequest, or355* SelectionNotify. */356{357register TkWindow *winPtr = (TkWindow *) tkwin;358TkDisplay *dispPtr = winPtr->dispPtr;359Tcl_Interp *interp;360361/*362* Case #1: SelectionClear events.363*/364365if (eventPtr->type == SelectionClear) {366TkSelClearSelection(tkwin, eventPtr);367}368369/*370* Case #2: SelectionNotify events. Call the relevant procedure371* to handle the incoming selection.372*/373374if (eventPtr->type == SelectionNotify) {375register TkSelRetrievalInfo *retrPtr;376char *propInfo;377Atom type;378int format, result;379unsigned long numItems, bytesAfter;380381for (retrPtr = pendingRetrievals; ; retrPtr = retrPtr->nextPtr) {382if (retrPtr == NULL) {383return;384}385if ((retrPtr->winPtr == winPtr)386&& (retrPtr->selection == eventPtr->xselection.selection)387&& (retrPtr->target == eventPtr->xselection.target)388&& (retrPtr->result == -1)) {389if (retrPtr->property == eventPtr->xselection.property) {390break;391}392if (eventPtr->xselection.property == None) {393Tcl_SetResult(retrPtr->interp, (char *) NULL, TCL_STATIC);394Tcl_AppendResult(retrPtr->interp,395Tk_GetAtomName(tkwin, retrPtr->selection),396" selection doesn't exist or form \"",397Tk_GetAtomName(tkwin, retrPtr->target),398"\" not defined", (char *) NULL);399retrPtr->result = TCL_ERROR;400return;401}402}403}404405propInfo = NULL;406result = XGetWindowProperty(eventPtr->xselection.display,407eventPtr->xselection.requestor, retrPtr->property,4080, MAX_PROP_WORDS, False, (Atom) AnyPropertyType,409&type, &format, &numItems, &bytesAfter,410(unsigned char **) &propInfo);411if ((result != Success) || (type == None)) {412return;413}414if (bytesAfter != 0) {415Tcl_SetResult(retrPtr->interp, "selection property too large",416TCL_STATIC);417retrPtr->result = TCL_ERROR;418XFree(propInfo);419return;420}421if ((type == XA_STRING) || (type == dispPtr->textAtom)422|| (type == dispPtr->compoundTextAtom)) {423if (format != 8) {424sprintf(retrPtr->interp->result,425"bad format for string selection: wanted \"8\", got \"%d\"",426format);427retrPtr->result = TCL_ERROR;428return;429}430interp = retrPtr->interp;431Tcl_Preserve((ClientData) interp);432retrPtr->result = (*retrPtr->proc)(retrPtr->clientData,433interp, propInfo);434Tcl_Release((ClientData) interp);435} else if (type == dispPtr->incrAtom) {436437/*438* It's a !?#@!?!! INCR-style reception. Arrange to receive439* the selection in pieces, using the ICCCM protocol, then440* hang around until either the selection is all here or a441* timeout occurs.442*/443444retrPtr->idleTime = 0;445Tk_CreateEventHandler(tkwin, PropertyChangeMask, SelRcvIncrProc,446(ClientData) retrPtr);447XDeleteProperty(Tk_Display(tkwin), Tk_WindowId(tkwin),448retrPtr->property);449while (retrPtr->result == -1) {450Tcl_DoOneEvent(0);451}452Tk_DeleteEventHandler(tkwin, PropertyChangeMask, SelRcvIncrProc,453(ClientData) retrPtr);454} else {455char *string;456457if (format != 32) {458sprintf(retrPtr->interp->result,459"bad format for selection: wanted \"32\", got \"%d\"",460format);461retrPtr->result = TCL_ERROR;462return;463}464string = SelCvtFromX((long *) propInfo, (int) numItems, type,465(Tk_Window) winPtr);466interp = retrPtr->interp;467Tcl_Preserve((ClientData) interp);468retrPtr->result = (*retrPtr->proc)(retrPtr->clientData,469interp, string);470Tcl_Release((ClientData) interp);471ckfree(string);472}473XFree(propInfo);474return;475}476477/*478* Case #3: SelectionRequest events. Call ConvertSelection to479* do the dirty work.480*/481482if (eventPtr->type == SelectionRequest) {483ConvertSelection(winPtr, &eventPtr->xselectionrequest);484return;485}486}487488/*489*----------------------------------------------------------------------490*491* SelTimeoutProc --492*493* This procedure is invoked once every second while waiting for494* the selection to be returned. After a while it gives up and495* aborts the selection retrieval.496*497* Results:498* None.499*500* Side effects:501* A new timer callback is created to call us again in another502* second, unless time has expired, in which case an error is503* recorded for the retrieval.504*505*----------------------------------------------------------------------506*/507508static void509SelTimeoutProc(clientData)510ClientData clientData; /* Information about retrieval511* in progress. */512{513register TkSelRetrievalInfo *retrPtr = (TkSelRetrievalInfo *) clientData;514515/*516* Make sure that the retrieval is still in progress. Then517* see how long it's been since any sort of response was received518* from the other side.519*/520521if (retrPtr->result != -1) {522return;523}524retrPtr->idleTime++;525if (retrPtr->idleTime >= 5) {526527/*528* Use a careful procedure to store the error message, because529* the result could already be partially filled in with a partial530* selection return.531*/532533Tcl_SetResult(retrPtr->interp, "selection owner didn't respond",534TCL_STATIC);535retrPtr->result = TCL_ERROR;536} else {537retrPtr->timeout = Tcl_CreateTimerHandler(1000, SelTimeoutProc,538(ClientData) retrPtr);539}540}541542/*543*----------------------------------------------------------------------544*545* ConvertSelection --546*547* This procedure is invoked to handle SelectionRequest events.548* It responds to the requests, obeying the ICCCM protocols.549*550* Results:551* None.552*553* Side effects:554* Properties are created for the selection requestor, and a555* SelectionNotify event is generated for the selection556* requestor. In the event of long selections, this procedure557* implements INCR-mode transfers, using the ICCCM protocol.558*559*----------------------------------------------------------------------560*/561562static void563ConvertSelection(winPtr, eventPtr)564TkWindow *winPtr; /* Window that received the565* conversion request; may not be566* selection's current owner, be we567* set it to the current owner. */568register XSelectionRequestEvent *eventPtr;569/* Event describing request. */570{571XSelectionEvent reply; /* Used to notify requestor that572* selection info is ready. */573int multiple; /* Non-zero means a MULTIPLE request574* is being handled. */575IncrInfo incr; /* State of selection conversion. */576Atom singleInfo[2]; /* incr.multAtoms points here except577* for multiple conversions. */578int i;579Tk_ErrorHandler errorHandler;580TkSelectionInfo *infoPtr;581TkSelInProgress ip;582583errorHandler = Tk_CreateErrorHandler(eventPtr->display, -1, -1,-1,584(int (*)()) NULL, (ClientData) NULL);585586/*587* Initialize the reply event.588*/589590reply.type = SelectionNotify;591reply.serial = 0;592reply.send_event = True;593reply.display = eventPtr->display;594reply.requestor = eventPtr->requestor;595reply.selection = eventPtr->selection;596reply.target = eventPtr->target;597reply.property = eventPtr->property;598if (reply.property == None) {599reply.property = reply.target;600}601reply.time = eventPtr->time;602603for (infoPtr = winPtr->dispPtr->selectionInfoPtr; infoPtr != NULL;604infoPtr = infoPtr->nextPtr) {605if (infoPtr->selection == eventPtr->selection)606break;607}608if (infoPtr == NULL) {609goto refuse;610}611winPtr = (TkWindow *) infoPtr->owner;612613/*614* Figure out which kind(s) of conversion to perform. If handling615* a MULTIPLE conversion, then read the property describing which616* conversions to perform.617*/618619incr.winPtr = winPtr;620incr.selection = eventPtr->selection;621if (eventPtr->target != winPtr->dispPtr->multipleAtom) {622multiple = 0;623singleInfo[0] = reply.target;624singleInfo[1] = reply.property;625incr.multAtoms = singleInfo;626incr.numConversions = 1;627} else {628Atom type;629int format, result;630unsigned long bytesAfter;631632multiple = 1;633incr.multAtoms = NULL;634if (eventPtr->property == None) {635goto refuse;636}637result = XGetWindowProperty(eventPtr->display,638eventPtr->requestor, eventPtr->property,6390, MAX_PROP_WORDS, False, XA_ATOM,640&type, &format, &incr.numConversions, &bytesAfter,641(unsigned char **) &incr.multAtoms);642if ((result != Success) || (bytesAfter != 0) || (format != 32)643|| (type == None)) {644if (incr.multAtoms != NULL) {645XFree((char *) incr.multAtoms);646}647goto refuse;648}649incr.numConversions /= 2; /* Two atoms per conversion. */650}651652/*653* Loop through all of the requested conversions, and either return654* the entire converted selection, if it can be returned in a single655* bunch, or return INCR information only (the actual selection will656* be returned below).657*/658659incr.offsets = (int *) ckalloc((unsigned)660(incr.numConversions*sizeof(int)));661incr.numIncrs = 0;662for (i = 0; i < incr.numConversions; i++) {663Atom target, property, type;664long buffer[TK_SEL_WORDS_AT_ONCE];665register TkSelHandler *selPtr;666int numItems, format;667char *propPtr;668669target = incr.multAtoms[2*i];670property = incr.multAtoms[2*i + 1];671incr.offsets[i] = -1;672673for (selPtr = winPtr->selHandlerList; selPtr != NULL;674selPtr = selPtr->nextPtr) {675if ((selPtr->target == target)676&& (selPtr->selection == eventPtr->selection)) {677break;678}679}680681if (selPtr == NULL) {682/*683* Nobody seems to know about this kind of request. If684* it's of a sort that we can handle without any help, do685* it. Otherwise mark the request as an errror.686*/687688numItems = TkSelDefaultSelection(infoPtr, target, (char *) buffer,689TK_SEL_BYTES_AT_ONCE, &type);690if (numItems < 0) {691incr.multAtoms[2*i + 1] = None;692continue;693}694} else {695ip.selPtr = selPtr;696ip.nextPtr = pendingPtr;697pendingPtr = &ip;698type = selPtr->format;699numItems = (*selPtr->proc)(selPtr->clientData, 0,700(char *) buffer, TK_SEL_BYTES_AT_ONCE);701pendingPtr = ip.nextPtr;702if ((ip.selPtr == NULL) || (numItems < 0)) {703incr.multAtoms[2*i + 1] = None;704continue;705}706if (numItems > TK_SEL_BYTES_AT_ONCE) {707panic("selection handler returned too many bytes");708}709((char *) buffer)[numItems] = '\0';710}711712/*713* Got the selection; store it back on the requestor's property.714*/715716if (numItems == TK_SEL_BYTES_AT_ONCE) {717/*718* Selection is too big to send at once; start an719* INCR-mode transfer.720*/721722incr.numIncrs++;723type = winPtr->dispPtr->incrAtom;724buffer[0] = SelectionSize(selPtr);725if (buffer[0] == 0) {726incr.multAtoms[2*i + 1] = None;727continue;728}729numItems = 1;730propPtr = (char *) buffer;731format = 32;732incr.offsets[i] = 0;733} else if (type == XA_STRING) {734propPtr = (char *) buffer;735format = 8;736} else {737propPtr = (char *) SelCvtToX((char *) buffer,738type, (Tk_Window) winPtr, &numItems);739format = 32;740}741XChangeProperty(reply.display, reply.requestor,742property, type, format, PropModeReplace,743(unsigned char *) propPtr, numItems);744if (propPtr != (char *) buffer) {745ckfree(propPtr);746}747}748749/*750* Send an event back to the requestor to indicate that the751* first stage of conversion is complete (everything is done752* except for long conversions that have to be done in INCR753* mode).754*/755756if (incr.numIncrs > 0) {757XSelectInput(reply.display, reply.requestor, PropertyChangeMask);758incr.timeout = Tcl_CreateTimerHandler(1000, IncrTimeoutProc,759(ClientData) &incr);760incr.idleTime = 0;761incr.reqWindow = reply.requestor;762incr.time = infoPtr->time;763incr.nextPtr = pendingIncrs;764pendingIncrs = &incr;765}766if (multiple) {767XChangeProperty(reply.display, reply.requestor, reply.property,768XA_ATOM, 32, PropModeReplace,769(unsigned char *) incr.multAtoms,770(int) incr.numConversions*2);771} else {772773/*774* Not a MULTIPLE request. The first property in "multAtoms"775* got set to None if there was an error in conversion.776*/777778reply.property = incr.multAtoms[1];779}780XSendEvent(reply.display, reply.requestor, False, 0, (XEvent *) &reply);781Tk_DeleteErrorHandler(errorHandler);782783/*784* Handle any remaining INCR-mode transfers. This all happens785* in callbacks to TkSelPropProc, so just wait until the number786* of uncompleted INCR transfers drops to zero.787*/788789if (incr.numIncrs > 0) {790IncrInfo *incrPtr2;791792while (incr.numIncrs > 0) {793Tcl_DoOneEvent(0);794}795Tcl_DeleteTimerHandler(incr.timeout);796errorHandler = Tk_CreateErrorHandler(winPtr->display,797-1, -1,-1, (int (*)()) NULL, (ClientData) NULL);798XSelectInput(reply.display, reply.requestor, 0L);799Tk_DeleteErrorHandler(errorHandler);800if (pendingIncrs == &incr) {801pendingIncrs = incr.nextPtr;802} else {803for (incrPtr2 = pendingIncrs; incrPtr2 != NULL;804incrPtr2 = incrPtr2->nextPtr) {805if (incrPtr2->nextPtr == &incr) {806incrPtr2->nextPtr = incr.nextPtr;807break;808}809}810}811}812813/*814* All done. Cleanup and return.815*/816817ckfree((char *) incr.offsets);818if (multiple) {819XFree((char *) incr.multAtoms);820}821return;822823/*824* An error occurred. Send back a refusal message.825*/826827refuse:828reply.property = None;829XSendEvent(reply.display, reply.requestor, False, 0, (XEvent *) &reply);830Tk_DeleteErrorHandler(errorHandler);831return;832}833834/*835*----------------------------------------------------------------------836*837* SelRcvIncrProc --838*839* This procedure handles the INCR protocol on the receiving840* side. It is invoked in response to property changes on841* the requestor's window (which hopefully are because a new842* chunk of the selection arrived).843*844* Results:845* None.846*847* Side effects:848* If a new piece of selection has arrived, a procedure is849* invoked to deal with that piece. When the whole selection850* is here, a flag is left for the higher-level procedure that851* initiated the selection retrieval.852*853*----------------------------------------------------------------------854*/855856static void857SelRcvIncrProc(clientData, eventPtr)858ClientData clientData; /* Information about retrieval. */859register XEvent *eventPtr; /* X PropertyChange event. */860{861register TkSelRetrievalInfo *retrPtr = (TkSelRetrievalInfo *) clientData;862char *propInfo;863Atom type;864int format, result;865unsigned long numItems, bytesAfter;866Tcl_Interp *interp;867868if ((eventPtr->xproperty.atom != retrPtr->property)869|| (eventPtr->xproperty.state != PropertyNewValue)870|| (retrPtr->result != -1)) {871return;872}873propInfo = NULL;874result = XGetWindowProperty(eventPtr->xproperty.display,875eventPtr->xproperty.window, retrPtr->property, 0, MAX_PROP_WORDS,876True, (Atom) AnyPropertyType, &type, &format, &numItems,877&bytesAfter, (unsigned char **) &propInfo);878if ((result != Success) || (type == None)) {879return;880}881if (bytesAfter != 0) {882Tcl_SetResult(retrPtr->interp, "selection property too large",883TCL_STATIC);884retrPtr->result = TCL_ERROR;885goto done;886}887if (numItems == 0) {888retrPtr->result = TCL_OK;889} else if ((type == XA_STRING)890|| (type == retrPtr->winPtr->dispPtr->textAtom)891|| (type == retrPtr->winPtr->dispPtr->compoundTextAtom)) {892if (format != 8) {893Tcl_SetResult(retrPtr->interp, (char *) NULL, TCL_STATIC);894sprintf(retrPtr->interp->result,895"bad format for string selection: wanted \"8\", got \"%d\"",896format);897retrPtr->result = TCL_ERROR;898goto done;899}900interp = retrPtr->interp;901Tcl_Preserve((ClientData) interp);902result = (*retrPtr->proc)(retrPtr->clientData, interp, propInfo);903Tcl_Release((ClientData) interp);904if (result != TCL_OK) {905retrPtr->result = result;906}907} else {908char *string;909910if (format != 32) {911Tcl_SetResult(retrPtr->interp, (char *) NULL, TCL_STATIC);912sprintf(retrPtr->interp->result,913"bad format for selection: wanted \"32\", got \"%d\"",914format);915retrPtr->result = TCL_ERROR;916goto done;917}918string = SelCvtFromX((long *) propInfo, (int) numItems, type,919(Tk_Window) retrPtr->winPtr);920interp = retrPtr->interp;921Tcl_Preserve((ClientData) interp);922result = (*retrPtr->proc)(retrPtr->clientData, interp, string);923Tcl_Release((ClientData) interp);924if (result != TCL_OK) {925retrPtr->result = result;926}927ckfree(string);928}929930done:931XFree(propInfo);932retrPtr->idleTime = 0;933}934935/*936*----------------------------------------------------------------------937*938* SelectionSize --939*940* This procedure is called when the selection is too large to941* send in a single buffer; it computes the total length of942* the selection in bytes.943*944* Results:945* The return value is the number of bytes in the selection946* given by selPtr.947*948* Side effects:949* The selection is retrieved from its current owner (this is950* the only way to compute its size).951*952*----------------------------------------------------------------------953*/954955static int956SelectionSize(selPtr)957TkSelHandler *selPtr; /* Information about how to retrieve958* the selection whose size is wanted. */959{960char buffer[TK_SEL_BYTES_AT_ONCE+1];961int size, chunkSize;962TkSelInProgress ip;963964size = TK_SEL_BYTES_AT_ONCE;965ip.selPtr = selPtr;966ip.nextPtr = pendingPtr;967pendingPtr = &ip;968do {969chunkSize = (*selPtr->proc)(selPtr->clientData, size,970(char *) buffer, TK_SEL_BYTES_AT_ONCE);971if (ip.selPtr == NULL) {972size = 0;973break;974}975size += chunkSize;976} while (chunkSize == TK_SEL_BYTES_AT_ONCE);977pendingPtr = ip.nextPtr;978return size;979}980981/*982*----------------------------------------------------------------------983*984* IncrTimeoutProc --985*986* This procedure is invoked once a second while sending the987* selection to a requestor in INCR mode. After a while it988* gives up and aborts the selection operation.989*990* Results:991* None.992*993* Side effects:994* A new timeout gets registered so that this procedure gets995* called again in another second, unless too many seconds996* have elapsed, in which case incrPtr is marked as "all done".997*998*----------------------------------------------------------------------999*/10001001static void1002IncrTimeoutProc(clientData)1003ClientData clientData; /* Information about INCR-mode1004* selection retrieval for which1005* we are selection owner. */1006{1007register IncrInfo *incrPtr = (IncrInfo *) clientData;10081009incrPtr->idleTime++;1010if (incrPtr->idleTime >= 5) {1011incrPtr->numIncrs = 0;1012} else {1013incrPtr->timeout = Tcl_CreateTimerHandler(1000, IncrTimeoutProc,1014(ClientData) incrPtr);1015}1016}10171018/*1019*----------------------------------------------------------------------1020*1021* SelCvtToX --1022*1023* Given a selection represented as a string (the normal Tcl form),1024* convert it to the ICCCM-mandated format for X, depending on1025* the type argument. This procedure and SelCvtFromX are inverses.1026*1027* Results:1028* The return value is a malloc'ed buffer holding a value1029* equivalent to "string", but formatted as for "type". It is1030* the caller's responsibility to free the string when done with1031* it. The word at *numLongsPtr is filled in with the number of1032* 32-bit words returned in the result.1033*1034* Side effects:1035* None.1036*1037*----------------------------------------------------------------------1038*/10391040static long *1041SelCvtToX(string, type, tkwin, numLongsPtr)1042char *string; /* String representation of selection. */1043Atom type; /* Atom specifying the X format that is1044* desired for the selection. Should not1045* be XA_STRING (if so, don't bother calling1046* this procedure at all). */1047Tk_Window tkwin; /* Window that governs atom conversion. */1048int *numLongsPtr; /* Number of 32-bit words contained in the1049* result. */1050{1051register char *p;1052char *field;1053int numFields;1054long *propPtr, *longPtr;1055#define MAX_ATOM_NAME_LENGTH 1001056char atomName[MAX_ATOM_NAME_LENGTH+1];10571058/*1059* The string is assumed to consist of fields separated by spaces.1060* The property gets generated by converting each field to an1061* integer number, in one of two ways:1062* 1. If type is XA_ATOM, convert each field to its corresponding1063* atom.1064* 2. If type is anything else, convert each field from an ASCII number1065* to a 32-bit binary number.1066*/10671068numFields = 1;1069for (p = string; *p != 0; p++) {1070if (isspace(UCHAR(*p))) {1071numFields++;1072}1073}1074propPtr = (long *) ckalloc((unsigned) numFields*sizeof(long));10751076/*1077* Convert the fields one-by-one.1078*/10791080for (longPtr = propPtr, *numLongsPtr = 0, p = string;1081; longPtr++, (*numLongsPtr)++) {1082while (isspace(UCHAR(*p))) {1083p++;1084}1085if (*p == 0) {1086break;1087}1088field = p;1089while ((*p != 0) && !isspace(UCHAR(*p))) {1090p++;1091}1092if (type == XA_ATOM) {1093int length;10941095length = p - field;1096if (length > MAX_ATOM_NAME_LENGTH) {1097length = MAX_ATOM_NAME_LENGTH;1098}1099strncpy(atomName, field, (unsigned) length);1100atomName[length] = 0;1101*longPtr = (long) Tk_InternAtom(tkwin, atomName);1102} else {1103char *dummy;11041105*longPtr = strtol(field, &dummy, 0);1106}1107}1108return propPtr;1109}11101111/*1112*----------------------------------------------------------------------1113*1114* SelCvtFromX --1115*1116* Given an X property value, formatted as a collection of 32-bit1117* values according to "type" and the ICCCM conventions, convert1118* the value to a string suitable for manipulation by Tcl. This1119* procedure is the inverse of SelCvtToX.1120*1121* Results:1122* The return value is the string equivalent of "property". It is1123* malloc-ed and should be freed by the caller when no longer1124* needed.1125*1126* Side effects:1127* None.1128*1129*----------------------------------------------------------------------1130*/11311132static char *1133SelCvtFromX(propPtr, numValues, type, tkwin)1134register long *propPtr; /* Property value from X. */1135int numValues; /* Number of 32-bit values in property. */1136Atom type; /* Type of property Should not be1137* XA_STRING (if so, don't bother calling1138* this procedure at all). */1139Tk_Window tkwin; /* Window to use for atom conversion. */1140{1141char *result;1142int resultSpace, curSize, fieldSize;1143char *atomName;11441145/*1146* Convert each long in the property to a string value, which is1147* either the name of an atom (if type is XA_ATOM) or a hexadecimal1148* string. Make an initial guess about the size of the result, but1149* be prepared to enlarge the result if necessary.1150*/11511152resultSpace = 12*numValues+1;1153curSize = 0;1154atomName = ""; /* Not needed, but eliminates compiler warning. */1155result = (char *) ckalloc((unsigned) resultSpace);1156*result = '\0';1157for ( ; numValues > 0; propPtr++, numValues--) {1158if (type == XA_ATOM) {1159atomName = Tk_GetAtomName(tkwin, (Atom) *propPtr);1160fieldSize = strlen(atomName) + 1;1161} else {1162fieldSize = 12;1163}1164if (curSize+fieldSize >= resultSpace) {1165char *newResult;11661167resultSpace *= 2;1168if (curSize+fieldSize >= resultSpace) {1169resultSpace = curSize + fieldSize + 1;1170}1171newResult = (char *) ckalloc((unsigned) resultSpace);1172strncpy(newResult, result, (unsigned) curSize);1173ckfree(result);1174result = newResult;1175}1176if (curSize != 0) {1177result[curSize] = ' ';1178curSize++;1179}1180if (type == XA_ATOM) {1181strcpy(result+curSize, atomName);1182} else {1183sprintf(result+curSize, "0x%x", (unsigned int) *propPtr);1184}1185curSize += strlen(result+curSize);1186}1187return result;1188}118911901191