1190 lines
33 KiB
C
1190 lines
33 KiB
C
/*
|
||
* 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;
|
||
}
|