stk/Tk/unix/tkUnixSelect.c

1190 lines
33 KiB
C
Raw Normal View History

1996-09-27 06:29:02 -04:00
/*
* tkUnixSelect.c --
*
* This file contains X specific routines for manipulating
* selections.
*
* Copyright (c) 1995 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* SCCS: @(#) tkUnixSelect.c 1.5 96/03/29 14:14:31
*/
#include "tkInt.h"
#include "tkSelect.h"
/*
* When handling INCR-style selection retrievals, the selection owner
* uses the following data structure to communicate between the
* ConvertSelection procedure and TkSelPropProc.
*/
typedef struct IncrInfo {
TkWindow *winPtr; /* Window that owns selection. */
Atom selection; /* Selection that is being retrieved. */
Atom *multAtoms; /* Information about conversions to
* perform: one or more pairs of
* (target, property). This either
* points to a retrieved property (for
* MULTIPLE retrievals) or to a static
* array. */
unsigned long numConversions;
/* Number of entries in offsets (same as
* # of pairs in multAtoms). */
int *offsets; /* One entry for each pair in
* multAtoms; -1 means all data has
* been transferred for this
* conversion. -2 means only the
* final zero-length transfer still
* has to be done. Otherwise it is the
* offset of the next chunk of data
* to transfer. This array is malloc-ed. */
int numIncrs; /* Number of entries in offsets that
* aren't -1 (i.e. # of INCR-mode transfers
* not yet completed). */
Tcl_TimerToken timeout; /* Token for timer procedure. */
int idleTime; /* Number of seconds since we heard
* anything from the selection
* requestor. */
Window reqWindow; /* Requestor's window id. */
Time time; /* Timestamp corresponding to
* selection at beginning of request;
* used to abort transfer if selection
* changes. */
struct IncrInfo *nextPtr; /* Next in list of all INCR-style
* retrievals currently pending. */
} IncrInfo;
static IncrInfo *pendingIncrs = NULL;
/* List of all incr structures
* currently active. */
/*
* Largest property that we'll accept when sending or receiving the
* selection:
*/
#define MAX_PROP_WORDS 100000
static TkSelRetrievalInfo *pendingRetrievals = NULL;
/* List of all retrievals currently
* being waited for. */
/*
* Forward declarations for procedures defined in this file:
*/
static void ConvertSelection _ANSI_ARGS_((TkWindow *winPtr,
XSelectionRequestEvent *eventPtr));
static void IncrTimeoutProc _ANSI_ARGS_((ClientData clientData));
static char * SelCvtFromX _ANSI_ARGS_((long *propPtr, int numValues,
Atom type, Tk_Window tkwin));
static long * SelCvtToX _ANSI_ARGS_((char *string, Atom type,
Tk_Window tkwin, int *numLongsPtr));
static int SelectionSize _ANSI_ARGS_((TkSelHandler *selPtr));
static void SelRcvIncrProc _ANSI_ARGS_((ClientData clientData,
XEvent *eventPtr));
static void SelTimeoutProc _ANSI_ARGS_((ClientData clientData));
/*
*----------------------------------------------------------------------
*
* TkSelGetSelection --
*
* Retrieve the specified selection from another process.
*
* Results:
* The return value is a standard Tcl return value.
* If an error occurs (such as no selection exists)
* then an error message is left in interp->result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
TkSelGetSelection(interp, tkwin, selection, target, proc, clientData)
Tcl_Interp *interp; /* Interpreter to use for reporting
* errors. */
Tk_Window tkwin; /* Window on whose behalf to retrieve
* the selection (determines display
* from which to retrieve). */
Atom selection; /* Selection to retrieve. */
Atom target; /* Desired form in which selection
* is to be returned. */
Tk_GetSelProc *proc; /* Procedure to call to process the
* selection, once it has been retrieved. */
ClientData clientData; /* Arbitrary value to pass to proc. */
{
TkSelRetrievalInfo retr;
TkWindow *winPtr = (TkWindow *) tkwin;
TkDisplay *dispPtr = winPtr->dispPtr;
/*
* The selection is owned by some other process. To
* retrieve it, first record information about the retrieval
* in progress. Use an internal window as the requestor.
*/
retr.interp = interp;
if (dispPtr->clipWindow == NULL) {
int result;
result = TkClipInit(interp, dispPtr);
if (result != TCL_OK) {
return result;
}
}
retr.winPtr = (TkWindow *) dispPtr->clipWindow;
retr.selection = selection;
retr.property = selection;
retr.target = target;
retr.proc = proc;
retr.clientData = clientData;
retr.result = -1;
retr.idleTime = 0;
retr.nextPtr = pendingRetrievals;
pendingRetrievals = &retr;
/*
* Initiate the request for the selection. Note: can't use
* TkCurrentTime for the time. If we do, and this application hasn't
* received any X events in a long time, the current time will be way
* in the past and could even predate the time when the selection was
* made; if this happens, the request will be rejected.
*/
XConvertSelection(winPtr->display, retr.selection, retr.target,
retr.property, retr.winPtr->window, CurrentTime);
/*
* Enter a loop processing X events until the selection
* has been retrieved and processed. If no response is
* received within a few seconds, then timeout.
*/
retr.timeout = Tcl_CreateTimerHandler(1000, SelTimeoutProc,
(ClientData) &retr);
while (retr.result == -1) {
Tcl_DoOneEvent(0);
}
Tcl_DeleteTimerHandler(retr.timeout);
/*
* Unregister the information about the selection retrieval
* in progress.
*/
if (pendingRetrievals == &retr) {
pendingRetrievals = retr.nextPtr;
} else {
TkSelRetrievalInfo *retrPtr;
for (retrPtr = pendingRetrievals; retrPtr != NULL;
retrPtr = retrPtr->nextPtr) {
if (retrPtr->nextPtr == &retr) {
retrPtr->nextPtr = retr.nextPtr;
break;
}
}
}
return retr.result;
}
/*
*----------------------------------------------------------------------
*
* TkSelPropProc --
*
* This procedure is invoked when property-change events
* occur on windows not known to the toolkit. Its function
* is to implement the sending side of the INCR selection
* retrieval protocol when the selection requestor deletes
* the property containing a part of the selection.
*
* Results:
* None.
*
* Side effects:
* If the property that is receiving the selection was just
* deleted, then a new piece of the selection is fetched and
* placed in the property, until eventually there's no more
* selection to fetch.
*
*----------------------------------------------------------------------
*/
void
TkSelPropProc(eventPtr)
register XEvent *eventPtr; /* X PropertyChange event. */
{
register IncrInfo *incrPtr;
int i, format;
Atom target, formatType;
register TkSelHandler *selPtr;
long buffer[TK_SEL_WORDS_AT_ONCE];
int numItems;
char *propPtr;
Tk_ErrorHandler errorHandler;
/*
* See if this event announces the deletion of a property being
* used for an INCR transfer. If so, then add the next chunk of
* data to the property.
*/
if (eventPtr->xproperty.state != PropertyDelete) {
return;
}
for (incrPtr = pendingIncrs; incrPtr != NULL;
incrPtr = incrPtr->nextPtr) {
if (incrPtr->reqWindow != eventPtr->xproperty.window) {
continue;
}
for (i = 0; i < incrPtr->numConversions; i++) {
if ((eventPtr->xproperty.atom != incrPtr->multAtoms[2*i + 1])
|| (incrPtr->offsets[i] == -1)){
continue;
}
target = incrPtr->multAtoms[2*i];
incrPtr->idleTime = 0;
for (selPtr = incrPtr->winPtr->selHandlerList; ;
selPtr = selPtr->nextPtr) {
if (selPtr == NULL) {
incrPtr->multAtoms[2*i + 1] = None;
incrPtr->offsets[i] = -1;
incrPtr->numIncrs --;
return;
}
if ((selPtr->target == target)
&& (selPtr->selection == incrPtr->selection)) {
formatType = selPtr->format;
if (incrPtr->offsets[i] == -2) {
numItems = 0;
((char *) buffer)[0] = 0;
} else {
TkSelInProgress ip;
ip.selPtr = selPtr;
ip.nextPtr = pendingPtr;
pendingPtr = &ip;
numItems = (*selPtr->proc)(selPtr->clientData,
incrPtr->offsets[i], (char *) buffer,
TK_SEL_BYTES_AT_ONCE);
pendingPtr = ip.nextPtr;
if (ip.selPtr == NULL) {
/*
* The selection handler deleted itself.
*/
return;
}
if (numItems > TK_SEL_BYTES_AT_ONCE) {
panic("selection handler returned too many bytes");
} else {
if (numItems < 0) {
numItems = 0;
}
}
((char *) buffer)[numItems] = '\0';
}
if (numItems < TK_SEL_BYTES_AT_ONCE) {
if (numItems <= 0) {
incrPtr->offsets[i] = -1;
incrPtr->numIncrs--;
} else {
incrPtr->offsets[i] = -2;
}
} else {
incrPtr->offsets[i] += numItems;
}
if (formatType == XA_STRING) {
propPtr = (char *) buffer;
format = 8;
} else {
propPtr = (char *) SelCvtToX((char *) buffer,
formatType, (Tk_Window) incrPtr->winPtr,
&numItems);
format = 32;
}
errorHandler = Tk_CreateErrorHandler(
eventPtr->xproperty.display, -1, -1, -1,
(int (*)()) NULL, (ClientData) NULL);
XChangeProperty(eventPtr->xproperty.display,
eventPtr->xproperty.window,
eventPtr->xproperty.atom, formatType,
format, PropModeReplace,
(unsigned char *) propPtr, numItems);
Tk_DeleteErrorHandler(errorHandler);
if (propPtr != (char *) buffer) {
ckfree(propPtr);
}
return;
}
}
}
}
}
/*
*--------------------------------------------------------------
*
* TkSelEventProc --
*
* This procedure is invoked whenever a selection-related
* event occurs. It does the lion's share of the work
* in implementing the selection protocol.
*
* Results:
* None.
*
* Side effects:
* Lots: depends on the type of event.
*
*--------------------------------------------------------------
*/
void
TkSelEventProc(tkwin, eventPtr)
Tk_Window tkwin; /* Window for which event was
* targeted. */
register XEvent *eventPtr; /* X event: either SelectionClear,
* SelectionRequest, or
* SelectionNotify. */
{
register TkWindow *winPtr = (TkWindow *) tkwin;
TkDisplay *dispPtr = winPtr->dispPtr;
Tcl_Interp *interp;
/*
* Case #1: SelectionClear events.
*/
if (eventPtr->type == SelectionClear) {
TkSelClearSelection(tkwin, eventPtr);
}
/*
* Case #2: SelectionNotify events. Call the relevant procedure
* to handle the incoming selection.
*/
if (eventPtr->type == SelectionNotify) {
register TkSelRetrievalInfo *retrPtr;
char *propInfo;
Atom type;
int format, result;
unsigned long numItems, bytesAfter;
for (retrPtr = pendingRetrievals; ; retrPtr = retrPtr->nextPtr) {
if (retrPtr == NULL) {
return;
}
if ((retrPtr->winPtr == winPtr)
&& (retrPtr->selection == eventPtr->xselection.selection)
&& (retrPtr->target == eventPtr->xselection.target)
&& (retrPtr->result == -1)) {
if (retrPtr->property == eventPtr->xselection.property) {
break;
}
if (eventPtr->xselection.property == None) {
Tcl_SetResult(retrPtr->interp, (char *) NULL, TCL_STATIC);
Tcl_AppendResult(retrPtr->interp,
Tk_GetAtomName(tkwin, retrPtr->selection),
" selection doesn't exist or form \"",
Tk_GetAtomName(tkwin, retrPtr->target),
"\" not defined", (char *) NULL);
retrPtr->result = TCL_ERROR;
return;
}
}
}
propInfo = NULL;
result = XGetWindowProperty(eventPtr->xselection.display,
eventPtr->xselection.requestor, retrPtr->property,
0, MAX_PROP_WORDS, False, (Atom) AnyPropertyType,
&type, &format, &numItems, &bytesAfter,
(unsigned char **) &propInfo);
if ((result != Success) || (type == None)) {
return;
}
if (bytesAfter != 0) {
Tcl_SetResult(retrPtr->interp, "selection property too large",
TCL_STATIC);
retrPtr->result = TCL_ERROR;
XFree(propInfo);
return;
}
if ((type == XA_STRING) || (type == dispPtr->textAtom)
|| (type == dispPtr->compoundTextAtom)) {
if (format != 8) {
sprintf(retrPtr->interp->result,
"bad format for string selection: wanted \"8\", got \"%d\"",
format);
retrPtr->result = TCL_ERROR;
return;
}
interp = retrPtr->interp;
Tcl_Preserve((ClientData) interp);
retrPtr->result = (*retrPtr->proc)(retrPtr->clientData,
interp, propInfo);
Tcl_Release((ClientData) interp);
} else if (type == dispPtr->incrAtom) {
/*
* It's a !?#@!?!! INCR-style reception. Arrange to receive
* the selection in pieces, using the ICCCM protocol, then
* hang around until either the selection is all here or a
* timeout occurs.
*/
retrPtr->idleTime = 0;
Tk_CreateEventHandler(tkwin, PropertyChangeMask, SelRcvIncrProc,
(ClientData) retrPtr);
XDeleteProperty(Tk_Display(tkwin), Tk_WindowId(tkwin),
retrPtr->property);
while (retrPtr->result == -1) {
Tcl_DoOneEvent(0);
}
Tk_DeleteEventHandler(tkwin, PropertyChangeMask, SelRcvIncrProc,
(ClientData) retrPtr);
} else {
char *string;
if (format != 32) {
sprintf(retrPtr->interp->result,
"bad format for selection: wanted \"32\", got \"%d\"",
format);
retrPtr->result = TCL_ERROR;
return;
}
string = SelCvtFromX((long *) propInfo, (int) numItems, type,
(Tk_Window) winPtr);
interp = retrPtr->interp;
Tcl_Preserve((ClientData) interp);
retrPtr->result = (*retrPtr->proc)(retrPtr->clientData,
interp, string);
Tcl_Release((ClientData) interp);
ckfree(string);
}
XFree(propInfo);
return;
}
/*
* Case #3: SelectionRequest events. Call ConvertSelection to
* do the dirty work.
*/
if (eventPtr->type == SelectionRequest) {
ConvertSelection(winPtr, &eventPtr->xselectionrequest);
return;
}
}
/*
*----------------------------------------------------------------------
*
* SelTimeoutProc --
*
* This procedure is invoked once every second while waiting for
* the selection to be returned. After a while it gives up and
* aborts the selection retrieval.
*
* Results:
* None.
*
* Side effects:
* A new timer callback is created to call us again in another
* second, unless time has expired, in which case an error is
* recorded for the retrieval.
*
*----------------------------------------------------------------------
*/
static void
SelTimeoutProc(clientData)
ClientData clientData; /* Information about retrieval
* in progress. */
{
register TkSelRetrievalInfo *retrPtr = (TkSelRetrievalInfo *) clientData;
/*
* Make sure that the retrieval is still in progress. Then
* see how long it's been since any sort of response was received
* from the other side.
*/
if (retrPtr->result != -1) {
return;
}
retrPtr->idleTime++;
if (retrPtr->idleTime >= 5) {
/*
* Use a careful procedure to store the error message, because
* the result could already be partially filled in with a partial
* selection return.
*/
Tcl_SetResult(retrPtr->interp, "selection owner didn't respond",
TCL_STATIC);
retrPtr->result = TCL_ERROR;
} else {
retrPtr->timeout = Tcl_CreateTimerHandler(1000, SelTimeoutProc,
(ClientData) retrPtr);
}
}
/*
*----------------------------------------------------------------------
*
* ConvertSelection --
*
* This procedure is invoked to handle SelectionRequest events.
* It responds to the requests, obeying the ICCCM protocols.
*
* Results:
* None.
*
* Side effects:
* Properties are created for the selection requestor, and a
* SelectionNotify event is generated for the selection
* requestor. In the event of long selections, this procedure
* implements INCR-mode transfers, using the ICCCM protocol.
*
*----------------------------------------------------------------------
*/
static void
ConvertSelection(winPtr, eventPtr)
TkWindow *winPtr; /* Window that received the
* conversion request; may not be
* selection's current owner, be we
* set it to the current owner. */
register XSelectionRequestEvent *eventPtr;
/* Event describing request. */
{
XSelectionEvent reply; /* Used to notify requestor that
* selection info is ready. */
int multiple; /* Non-zero means a MULTIPLE request
* is being handled. */
IncrInfo incr; /* State of selection conversion. */
Atom singleInfo[2]; /* incr.multAtoms points here except
* for multiple conversions. */
int i;
Tk_ErrorHandler errorHandler;
TkSelectionInfo *infoPtr;
TkSelInProgress ip;
errorHandler = Tk_CreateErrorHandler(eventPtr->display, -1, -1,-1,
(int (*)()) NULL, (ClientData) NULL);
/*
* Initialize the reply event.
*/
reply.type = SelectionNotify;
reply.serial = 0;
reply.send_event = True;
reply.display = eventPtr->display;
reply.requestor = eventPtr->requestor;
reply.selection = eventPtr->selection;
reply.target = eventPtr->target;
reply.property = eventPtr->property;
if (reply.property == None) {
reply.property = reply.target;
}
reply.time = eventPtr->time;
for (infoPtr = winPtr->dispPtr->selectionInfoPtr; infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (infoPtr->selection == eventPtr->selection)
break;
}
if (infoPtr == NULL) {
goto refuse;
}
winPtr = (TkWindow *) infoPtr->owner;
/*
* Figure out which kind(s) of conversion to perform. If handling
* a MULTIPLE conversion, then read the property describing which
* conversions to perform.
*/
incr.winPtr = winPtr;
incr.selection = eventPtr->selection;
if (eventPtr->target != winPtr->dispPtr->multipleAtom) {
multiple = 0;
singleInfo[0] = reply.target;
singleInfo[1] = reply.property;
incr.multAtoms = singleInfo;
incr.numConversions = 1;
} else {
Atom type;
int format, result;
unsigned long bytesAfter;
multiple = 1;
incr.multAtoms = NULL;
if (eventPtr->property == None) {
goto refuse;
}
result = XGetWindowProperty(eventPtr->display,
eventPtr->requestor, eventPtr->property,
0, MAX_PROP_WORDS, False, XA_ATOM,
&type, &format, &incr.numConversions, &bytesAfter,
(unsigned char **) &incr.multAtoms);
if ((result != Success) || (bytesAfter != 0) || (format != 32)
|| (type == None)) {
if (incr.multAtoms != NULL) {
XFree((char *) incr.multAtoms);
}
goto refuse;
}
incr.numConversions /= 2; /* Two atoms per conversion. */
}
/*
* Loop through all of the requested conversions, and either return
* the entire converted selection, if it can be returned in a single
* bunch, or return INCR information only (the actual selection will
* be returned below).
*/
incr.offsets = (int *) ckalloc((unsigned)
(incr.numConversions*sizeof(int)));
incr.numIncrs = 0;
for (i = 0; i < incr.numConversions; i++) {
Atom target, property, type;
long buffer[TK_SEL_WORDS_AT_ONCE];
register TkSelHandler *selPtr;
int numItems, format;
char *propPtr;
target = incr.multAtoms[2*i];
property = incr.multAtoms[2*i + 1];
incr.offsets[i] = -1;
for (selPtr = winPtr->selHandlerList; selPtr != NULL;
selPtr = selPtr->nextPtr) {
if ((selPtr->target == target)
&& (selPtr->selection == eventPtr->selection)) {
break;
}
}
if (selPtr == NULL) {
/*
* Nobody seems to know about this kind of request. If
* it's of a sort that we can handle without any help, do
* it. Otherwise mark the request as an errror.
*/
numItems = TkSelDefaultSelection(infoPtr, target, (char *) buffer,
TK_SEL_BYTES_AT_ONCE, &type);
if (numItems < 0) {
incr.multAtoms[2*i + 1] = None;
continue;
}
} else {
ip.selPtr = selPtr;
ip.nextPtr = pendingPtr;
pendingPtr = &ip;
type = selPtr->format;
numItems = (*selPtr->proc)(selPtr->clientData, 0,
(char *) buffer, TK_SEL_BYTES_AT_ONCE);
pendingPtr = ip.nextPtr;
if ((ip.selPtr == NULL) || (numItems < 0)) {
incr.multAtoms[2*i + 1] = None;
continue;
}
if (numItems > TK_SEL_BYTES_AT_ONCE) {
panic("selection handler returned too many bytes");
}
((char *) buffer)[numItems] = '\0';
}
/*
* Got the selection; store it back on the requestor's property.
*/
if (numItems == TK_SEL_BYTES_AT_ONCE) {
/*
* Selection is too big to send at once; start an
* INCR-mode transfer.
*/
incr.numIncrs++;
type = winPtr->dispPtr->incrAtom;
buffer[0] = SelectionSize(selPtr);
if (buffer[0] == 0) {
incr.multAtoms[2*i + 1] = None;
continue;
}
numItems = 1;
propPtr = (char *) buffer;
format = 32;
incr.offsets[i] = 0;
} else if (type == XA_STRING) {
propPtr = (char *) buffer;
format = 8;
} else {
propPtr = (char *) SelCvtToX((char *) buffer,
type, (Tk_Window) winPtr, &numItems);
format = 32;
}
XChangeProperty(reply.display, reply.requestor,
property, type, format, PropModeReplace,
(unsigned char *) propPtr, numItems);
if (propPtr != (char *) buffer) {
ckfree(propPtr);
}
}
/*
* Send an event back to the requestor to indicate that the
* first stage of conversion is complete (everything is done
* except for long conversions that have to be done in INCR
* mode).
*/
if (incr.numIncrs > 0) {
XSelectInput(reply.display, reply.requestor, PropertyChangeMask);
incr.timeout = Tcl_CreateTimerHandler(1000, IncrTimeoutProc,
(ClientData) &incr);
incr.idleTime = 0;
incr.reqWindow = reply.requestor;
incr.time = infoPtr->time;
incr.nextPtr = pendingIncrs;
pendingIncrs = &incr;
}
if (multiple) {
XChangeProperty(reply.display, reply.requestor, reply.property,
XA_ATOM, 32, PropModeReplace,
(unsigned char *) incr.multAtoms,
(int) incr.numConversions*2);
} else {
/*
* Not a MULTIPLE request. The first property in "multAtoms"
* got set to None if there was an error in conversion.
*/
reply.property = incr.multAtoms[1];
}
XSendEvent(reply.display, reply.requestor, False, 0, (XEvent *) &reply);
Tk_DeleteErrorHandler(errorHandler);
/*
* Handle any remaining INCR-mode transfers. This all happens
* in callbacks to TkSelPropProc, so just wait until the number
* of uncompleted INCR transfers drops to zero.
*/
if (incr.numIncrs > 0) {
IncrInfo *incrPtr2;
while (incr.numIncrs > 0) {
Tcl_DoOneEvent(0);
}
Tcl_DeleteTimerHandler(incr.timeout);
errorHandler = Tk_CreateErrorHandler(winPtr->display,
-1, -1,-1, (int (*)()) NULL, (ClientData) NULL);
XSelectInput(reply.display, reply.requestor, 0L);
Tk_DeleteErrorHandler(errorHandler);
if (pendingIncrs == &incr) {
pendingIncrs = incr.nextPtr;
} else {
for (incrPtr2 = pendingIncrs; incrPtr2 != NULL;
incrPtr2 = incrPtr2->nextPtr) {
if (incrPtr2->nextPtr == &incr) {
incrPtr2->nextPtr = incr.nextPtr;
break;
}
}
}
}
/*
* All done. Cleanup and return.
*/
ckfree((char *) incr.offsets);
if (multiple) {
XFree((char *) incr.multAtoms);
}
return;
/*
* An error occurred. Send back a refusal message.
*/
refuse:
reply.property = None;
XSendEvent(reply.display, reply.requestor, False, 0, (XEvent *) &reply);
Tk_DeleteErrorHandler(errorHandler);
return;
}
/*
*----------------------------------------------------------------------
*
* SelRcvIncrProc --
*
* This procedure handles the INCR protocol on the receiving
* side. It is invoked in response to property changes on
* the requestor's window (which hopefully are because a new
* chunk of the selection arrived).
*
* Results:
* None.
*
* Side effects:
* If a new piece of selection has arrived, a procedure is
* invoked to deal with that piece. When the whole selection
* is here, a flag is left for the higher-level procedure that
* initiated the selection retrieval.
*
*----------------------------------------------------------------------
*/
static void
SelRcvIncrProc(clientData, eventPtr)
ClientData clientData; /* Information about retrieval. */
register XEvent *eventPtr; /* X PropertyChange event. */
{
register TkSelRetrievalInfo *retrPtr = (TkSelRetrievalInfo *) clientData;
char *propInfo;
Atom type;
int format, result;
unsigned long numItems, bytesAfter;
Tcl_Interp *interp;
if ((eventPtr->xproperty.atom != retrPtr->property)
|| (eventPtr->xproperty.state != PropertyNewValue)
|| (retrPtr->result != -1)) {
return;
}
propInfo = NULL;
result = XGetWindowProperty(eventPtr->xproperty.display,
eventPtr->xproperty.window, retrPtr->property, 0, MAX_PROP_WORDS,
True, (Atom) AnyPropertyType, &type, &format, &numItems,
&bytesAfter, (unsigned char **) &propInfo);
if ((result != Success) || (type == None)) {
return;
}
if (bytesAfter != 0) {
Tcl_SetResult(retrPtr->interp, "selection property too large",
TCL_STATIC);
retrPtr->result = TCL_ERROR;
goto done;
}
if (numItems == 0) {
retrPtr->result = TCL_OK;
} else if ((type == XA_STRING)
|| (type == retrPtr->winPtr->dispPtr->textAtom)
|| (type == retrPtr->winPtr->dispPtr->compoundTextAtom)) {
if (format != 8) {
Tcl_SetResult(retrPtr->interp, (char *) NULL, TCL_STATIC);
sprintf(retrPtr->interp->result,
"bad format for string selection: wanted \"8\", got \"%d\"",
format);
retrPtr->result = TCL_ERROR;
goto done;
}
interp = retrPtr->interp;
Tcl_Preserve((ClientData) interp);
result = (*retrPtr->proc)(retrPtr->clientData, interp, propInfo);
Tcl_Release((ClientData) interp);
if (result != TCL_OK) {
retrPtr->result = result;
}
} else {
char *string;
if (format != 32) {
Tcl_SetResult(retrPtr->interp, (char *) NULL, TCL_STATIC);
sprintf(retrPtr->interp->result,
"bad format for selection: wanted \"32\", got \"%d\"",
format);
retrPtr->result = TCL_ERROR;
goto done;
}
string = SelCvtFromX((long *) propInfo, (int) numItems, type,
(Tk_Window) retrPtr->winPtr);
interp = retrPtr->interp;
Tcl_Preserve((ClientData) interp);
result = (*retrPtr->proc)(retrPtr->clientData, interp, string);
Tcl_Release((ClientData) interp);
if (result != TCL_OK) {
retrPtr->result = result;
}
ckfree(string);
}
done:
XFree(propInfo);
retrPtr->idleTime = 0;
}
/*
*----------------------------------------------------------------------
*
* SelectionSize --
*
* This procedure is called when the selection is too large to
* send in a single buffer; it computes the total length of
* the selection in bytes.
*
* Results:
* The return value is the number of bytes in the selection
* given by selPtr.
*
* Side effects:
* The selection is retrieved from its current owner (this is
* the only way to compute its size).
*
*----------------------------------------------------------------------
*/
static int
SelectionSize(selPtr)
TkSelHandler *selPtr; /* Information about how to retrieve
* the selection whose size is wanted. */
{
char buffer[TK_SEL_BYTES_AT_ONCE+1];
int size, chunkSize;
TkSelInProgress ip;
size = TK_SEL_BYTES_AT_ONCE;
ip.selPtr = selPtr;
ip.nextPtr = pendingPtr;
pendingPtr = &ip;
do {
chunkSize = (*selPtr->proc)(selPtr->clientData, size,
(char *) buffer, TK_SEL_BYTES_AT_ONCE);
if (ip.selPtr == NULL) {
size = 0;
break;
}
size += chunkSize;
} while (chunkSize == TK_SEL_BYTES_AT_ONCE);
pendingPtr = ip.nextPtr;
return size;
}
/*
*----------------------------------------------------------------------
*
* IncrTimeoutProc --
*
* This procedure is invoked once a second while sending the
* selection to a requestor in INCR mode. After a while it
* gives up and aborts the selection operation.
*
* Results:
* None.
*
* Side effects:
* A new timeout gets registered so that this procedure gets
* called again in another second, unless too many seconds
* have elapsed, in which case incrPtr is marked as "all done".
*
*----------------------------------------------------------------------
*/
static void
IncrTimeoutProc(clientData)
ClientData clientData; /* Information about INCR-mode
* selection retrieval for which
* we are selection owner. */
{
register IncrInfo *incrPtr = (IncrInfo *) clientData;
incrPtr->idleTime++;
if (incrPtr->idleTime >= 5) {
incrPtr->numIncrs = 0;
} else {
incrPtr->timeout = Tcl_CreateTimerHandler(1000, IncrTimeoutProc,
(ClientData) incrPtr);
}
}
/*
*----------------------------------------------------------------------
*
* SelCvtToX --
*
* Given a selection represented as a string (the normal Tcl form),
* convert it to the ICCCM-mandated format for X, depending on
* the type argument. This procedure and SelCvtFromX are inverses.
*
* Results:
* The return value is a malloc'ed buffer holding a value
* equivalent to "string", but formatted as for "type". It is
* the caller's responsibility to free the string when done with
* it. The word at *numLongsPtr is filled in with the number of
* 32-bit words returned in the result.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static long *
SelCvtToX(string, type, tkwin, numLongsPtr)
char *string; /* String representation of selection. */
Atom type; /* Atom specifying the X format that is
* desired for the selection. Should not
* be XA_STRING (if so, don't bother calling
* this procedure at all). */
Tk_Window tkwin; /* Window that governs atom conversion. */
int *numLongsPtr; /* Number of 32-bit words contained in the
* result. */
{
register char *p;
char *field;
int numFields;
long *propPtr, *longPtr;
#define MAX_ATOM_NAME_LENGTH 100
char atomName[MAX_ATOM_NAME_LENGTH+1];
/*
* The string is assumed to consist of fields separated by spaces.
* The property gets generated by converting each field to an
* integer number, in one of two ways:
* 1. If type is XA_ATOM, convert each field to its corresponding
* atom.
* 2. If type is anything else, convert each field from an ASCII number
* to a 32-bit binary number.
*/
numFields = 1;
for (p = string; *p != 0; p++) {
if (isspace(UCHAR(*p))) {
numFields++;
}
}
propPtr = (long *) ckalloc((unsigned) numFields*sizeof(long));
/*
* Convert the fields one-by-one.
*/
for (longPtr = propPtr, *numLongsPtr = 0, p = string;
; longPtr++, (*numLongsPtr)++) {
while (isspace(UCHAR(*p))) {
p++;
}
if (*p == 0) {
break;
}
field = p;
while ((*p != 0) && !isspace(UCHAR(*p))) {
p++;
}
if (type == XA_ATOM) {
int length;
length = p - field;
if (length > MAX_ATOM_NAME_LENGTH) {
length = MAX_ATOM_NAME_LENGTH;
}
strncpy(atomName, field, (unsigned) length);
atomName[length] = 0;
*longPtr = (long) Tk_InternAtom(tkwin, atomName);
} else {
char *dummy;
*longPtr = strtol(field, &dummy, 0);
}
}
return propPtr;
}
/*
*----------------------------------------------------------------------
*
* SelCvtFromX --
*
* Given an X property value, formatted as a collection of 32-bit
* values according to "type" and the ICCCM conventions, convert
* the value to a string suitable for manipulation by Tcl. This
* procedure is the inverse of SelCvtToX.
*
* Results:
* The return value is the string equivalent of "property". It is
* malloc-ed and should be freed by the caller when no longer
* needed.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static char *
SelCvtFromX(propPtr, numValues, type, tkwin)
register long *propPtr; /* Property value from X. */
int numValues; /* Number of 32-bit values in property. */
Atom type; /* Type of property Should not be
* XA_STRING (if so, don't bother calling
* this procedure at all). */
Tk_Window tkwin; /* Window to use for atom conversion. */
{
char *result;
int resultSpace, curSize, fieldSize;
char *atomName;
/*
* Convert each long in the property to a string value, which is
* either the name of an atom (if type is XA_ATOM) or a hexadecimal
* string. Make an initial guess about the size of the result, but
* be prepared to enlarge the result if necessary.
*/
resultSpace = 12*numValues+1;
curSize = 0;
atomName = ""; /* Not needed, but eliminates compiler warning. */
result = (char *) ckalloc((unsigned) resultSpace);
*result = '\0';
for ( ; numValues > 0; propPtr++, numValues--) {
if (type == XA_ATOM) {
atomName = Tk_GetAtomName(tkwin, (Atom) *propPtr);
fieldSize = strlen(atomName) + 1;
} else {
fieldSize = 12;
}
if (curSize+fieldSize >= resultSpace) {
char *newResult;
resultSpace *= 2;
if (curSize+fieldSize >= resultSpace) {
resultSpace = curSize + fieldSize + 1;
}
newResult = (char *) ckalloc((unsigned) resultSpace);
strncpy(newResult, result, (unsigned) curSize);
ckfree(result);
result = newResult;
}
if (curSize != 0) {
result[curSize] = ' ';
curSize++;
}
if (type == XA_ATOM) {
strcpy(result+curSize, atomName);
} else {
sprintf(result+curSize, "0x%x", (unsigned int) *propPtr);
}
curSize += strlen(result+curSize);
}
return result;
}