1 /*
2  * tkUnixSelect.c --
3  *
4  *	This file contains X specific routines for manipulating selections.
5  *
6  * Copyright (c) 1995-1997 Sun Microsystems, Inc.
7  *
8  * See the file "license.terms" for information on usage and redistribution of
9  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  */
11 
12 #include "tk.h"
13 #include "X11/Xutil.h"
14 
15 /*
16  * Forward declarations for functions defined in this file:
17  */
18 typedef struct {
19   Tcl_Interp     *interp;
20   Tk_GetSelProc  *proc;
21   ClientData      clientData;
22   Tcl_TimerToken  timeout;
23   Tk_Window       tkwin;
24   Atom            property;
25   int             result;
26   int             idleTime;
27 } TkDND_ProcDetail;
28 
29 void TkDND_SelectionNotifyEventProc(ClientData clientData, XEvent *eventPtr);
30 void TkDND_PropertyNotifyEventProc(ClientData clientData, XEvent *eventPtr);
31 static void TkDND_SelTimeoutProc(ClientData clientData);
32 
maxSelectionIncr(Display * dpy)33 static inline int maxSelectionIncr(Display *dpy) {
34   return XMaxRequestSize(dpy) > 65536 ? 65536*4 :
35                                XMaxRequestSize(dpy)*4 - 100;
36 }; /* maxSelectionIncr */
37 
TkDND_ClipboardReadProperty(Tk_Window tkwin,Atom property,int deleteProperty,TkDND_ProcDetail * detail,int * size,Atom * type,int * format)38 int TkDND_ClipboardReadProperty(Tk_Window tkwin,
39                                 Atom property, int deleteProperty,
40                                 TkDND_ProcDetail *detail,
41                                 int *size, Atom *type, int *format) {
42     Display *display = Tk_Display(tkwin);
43     Window   win     = Tk_WindowId(tkwin);
44     int      maxsize = maxSelectionIncr(display);
45     unsigned long    bytes_left; // bytes_after
46     unsigned long    length;     // nitems
47     unsigned char   *data;
48     Atom     dummy_type;
49     int      dummy_format;
50     int      r;
51     Tcl_DString *buffer = (Tcl_DString *) detail->clientData;
52 
53     if (!type)                                // allow null args
54         type = &dummy_type;
55     if (!format)
56         format = &dummy_format;
57 
58     // Don't read anything, just get the size of the property data
59     r = XGetWindowProperty(display, win, property, 0, 0, False,
60                             AnyPropertyType, type, format,
61                             &length, &bytes_left, &data);
62     if (r != Success || (type && *type == None)) {
63         return 0;
64     }
65     XFree((char*)data);
66 
67     int offset = 0, format_inc = 1, proplen = bytes_left;
68 
69     switch (*format) {
70     case 8:
71     default:
72         format_inc = sizeof(char) / 1;
73         break;
74 
75     case 16:
76         format_inc = sizeof(short) / 2;
77         proplen   *= sizeof(short) / 2;
78         break;
79 
80     case 32:
81         format_inc = sizeof(long) / 4;
82         proplen   *= sizeof(long) / 4;
83         break;
84     }
85 
86     while (bytes_left) {
87       r = XGetWindowProperty(display, win, property, offset, maxsize/4,
88                              False, AnyPropertyType, type, format,
89                              &length, &bytes_left, &data);
90       if (r != Success || (type && *type == None))
91           break;
92       switch (*format) {
93         case 8:
94         default:
95           offset += length / (32 / *format);
96           length *= format_inc * (*format) / 8;
97           Tcl_DStringAppend(buffer, (char *) data, length);
98           break;
99         case 16: {
100           register unsigned short *propPtr = (unsigned short *) data;
101           for (; length > 0; propPtr++, length--) {
102             char buf[12];
103 
104 	    sprintf(buf, "0x%04x", (unsigned short) *propPtr);
105 	    Tcl_DStringAppendElement(buffer, buf);
106           }
107           Tcl_DStringAppend(buffer, " ", 1);
108           break;
109         }
110         case 32: {
111           register unsigned long *propPtr = (unsigned long *) data;
112           for (; length > 0; propPtr++, length--) {
113             char buf[12];
114 
115 	    sprintf(buf, "0x%x", (unsigned int) *propPtr);
116 	    Tcl_DStringAppendElement(buffer, buf);
117           }
118           Tcl_DStringAppend(buffer, " ", 1);
119           break;
120         }
121       }
122 
123       XFree((char*)data);
124     }
125 #if 0
126     printf("Selection details:\n");
127     printf("  type: %s\n", XGetAtomName(display, *type));
128     printf("  format: %d %s\n", *format, XGetAtomName(display, *format));
129     printf("  length: %d\n", Tcl_DStringLength(buffer));
130     printf("  data: \"%s\"\n", Tcl_DStringValue(buffer));
131 #endif
132 
133     if (*format == 8 && *type == Tk_InternAtom(tkwin, "COMPOUND_TEXT")) {
134       // convert COMPOUND_TEXT to a multibyte string
135       XTextProperty textprop;
136       textprop.encoding = *type;
137       textprop.format = *format;
138       textprop.nitems = Tcl_DStringLength(buffer);
139       textprop.value = (unsigned char *) Tcl_DStringValue(buffer);
140 
141       char **list_ret = 0;
142       int count;
143       if (XmbTextPropertyToTextList(display, &textprop, &list_ret,
144                    &count) == Success && count && list_ret) {
145         Tcl_DStringFree(buffer);
146         Tcl_DStringInit(buffer);
147         Tcl_DStringAppend(buffer, list_ret[0], -1);
148       }
149       if (list_ret) XFreeStringList(list_ret);
150     }
151 
152     // correct size, not 0-term.
153     if (size) *size = Tcl_DStringLength(buffer);
154     if (deleteProperty) XDeleteProperty(display, win, property);
155     //XFlush(display);
156     return 1;
157 }; /* TkDND_ClipboardReadProperty */
158 
TkDND_ClipboardReadIncrementalProperty(Tk_Window tkwin,Atom property,TkDND_ProcDetail * detail)159 int TkDND_ClipboardReadIncrementalProperty(Tk_Window tkwin,
160                                            Atom property,
161                                            TkDND_ProcDetail *detail) {
162   TkDND_ProcDetail detail2;
163   Tcl_DString     *buffer  = (Tcl_DString *) detail->clientData;
164   detail2.interp           = detail->interp;
165   detail2.tkwin            = detail->tkwin;
166   detail2.property         = detail->property;
167   detail2.proc             = NULL;
168   detail2.clientData       = buffer;
169   detail2.result           = -1;
170   detail2.idleTime         = 0;
171   Tcl_DStringFree(buffer);
172   Tcl_DStringInit(buffer);
173 
174   //XFlush(display);
175   /* Install a handler for PropertyNotify events... */
176   Tk_CreateEventHandler(tkwin, PropertyNotify,
177                         TkDND_PropertyNotifyEventProc, &detail2);
178   /*
179    * Enter a loop processing X events until the selection has been retrieved
180    * and processed. If no response is received within a few seconds, then
181    * timeout.
182    */
183   detail2.timeout = Tcl_CreateTimerHandler(1000, TkDND_SelTimeoutProc,
184                                            &detail2);
185   while (detail2.result == -1) {
186     //XFlush(display);
187     Tcl_DoOneEvent(0);
188   }
189   Tk_DeleteEventHandler(tkwin, PropertyNotify,
190                         TkDND_PropertyNotifyEventProc, &detail2);
191   if (detail2.timeout) Tcl_DeleteTimerHandler(detail2.timeout);
192   return detail2.result;
193 }; /* TkDND_ClipboardReadIncrementalProperty */
194 
TkDND_SelectionNotifyEventProc(ClientData clientData,XEvent * eventPtr)195 void TkDND_SelectionNotifyEventProc(ClientData clientData, XEvent *eventPtr) {
196   TkDND_ProcDetail *detail = (TkDND_ProcDetail *) clientData;
197   int status, size, format;
198   Atom type;
199 
200   status = TkDND_ClipboardReadProperty(detail->tkwin, detail->property, 1,
201                                        detail, &size, &type, &format);
202   if (status) {
203 #ifdef TKDND_DebugSelectionRequests
204     if (eventPtr != NULL) {
205     printf("SelectionNotify: selection: %s, target: %s, property: %s,\
206             type: %s, format: %d\n",
207             Tk_GetAtomName(detail->tkwin, eventPtr->xselection.selection),
208             Tk_GetAtomName(detail->tkwin, eventPtr->xselection.target),
209             Tk_GetAtomName(detail->tkwin, eventPtr->xselection.property),
210             Tk_GetAtomName(detail->tkwin, type), format);
211     }
212 #endif /* TKDND_DebugSelectionRequests */
213     if (type == Tk_InternAtom(detail->tkwin, "INCR")) {
214       status = TkDND_ClipboardReadIncrementalProperty(detail->tkwin,
215                                        detail->property, detail);
216     }
217   }
218   if (status) detail->result = TCL_OK;
219   else {
220     /* Do not report the error if this has not be called by a
221      *  SelectionNotify event... */
222     if (eventPtr != NULL) detail->result = TCL_ERROR;
223   }
224 }; /* TkDND_SelectionNotifyEventProc */
225 
TkDND_PropertyNotifyEventProc(ClientData clientData,XEvent * eventPtr)226 void TkDND_PropertyNotifyEventProc(ClientData clientData, XEvent *eventPtr) {
227   TkDND_ProcDetail *detail = (TkDND_ProcDetail *) clientData;
228   Tcl_DString      *buffer = (Tcl_DString *) detail->clientData;
229   Tcl_DString       ds;
230   int status, size, format;
231   Atom type;
232   if (eventPtr->xproperty.atom != detail->property ||
233       eventPtr->xproperty.state != PropertyNewValue) return;
234   /* We will call TkDND_ClipboardReadProperty to read the property. Ensure that
235    * a temporary DString will be used... */
236   Tcl_DStringInit(&ds);
237   detail->clientData = &ds;
238   status = TkDND_ClipboardReadProperty(detail->tkwin, detail->property, 1,
239                                        detail, &size, &type, &format);
240   detail->clientData = buffer;
241   if (status) {
242     if (size == 0) {
243       /* We are done! */
244       detail->result = status;
245     } else {
246       Tcl_DStringAppend(buffer, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
247     }
248   } else {
249     /* An error occurred... */
250     detail->result = status;
251   }
252   Tcl_DStringFree(&ds);
253 }; /* TkDND_PropertyNotifyEventProc */
254 
255 /*
256  *----------------------------------------------------------------------
257  *
258  * TkDNDSelGetSelection --
259  *
260  *	Retrieve the specified selection from another process.
261  *
262  * Results:
263  *	The return value is a standard Tcl return value. If an error occurs
264  *	(such as no selection exists) then an error message is left in the
265  *	interp's result.
266  *
267  * Side effects:
268  *	None.
269  *
270  *----------------------------------------------------------------------
271  */
272 
273 int
TkDNDSelGetSelection(Tcl_Interp * interp,Tk_Window tkwin,Atom selection,Atom target,Time time,Tk_GetSelProc * proc,ClientData clientData)274 TkDNDSelGetSelection(
275     Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
276     Tk_Window tkwin,		/* Window on whose behalf to retrieve the
277 				 * selection (determines display from which to
278 				 * retrieve). */
279     Atom selection,		/* Selection to retrieve. */
280     Atom target,		/* Desired form in which selection is to be
281 				 * returned. */
282     Time time,
283     Tk_GetSelProc *proc,	/* Function to call to process the selection,
284 				 * once it has been retrieved. */
285     ClientData clientData)	/* Arbitrary value to pass to proc. */
286 {
287     TkDND_ProcDetail detail;
288     Tk_Window sel_tkwin = Tk_MainWindow(interp);
289     Display *display    = Tk_Display(tkwin);
290     detail.interp       = interp;
291     detail.tkwin        = sel_tkwin;
292     detail.property     = selection;
293     detail.proc         = proc;
294     detail.clientData   = clientData;
295     detail.result       = -1;
296     detail.idleTime     = 0;
297 
298     XFlush(display);
299     if (XGetSelectionOwner(display, selection) == None) {
300       Tcl_SetResult(interp, "no owner for selection", TCL_STATIC);
301       return TCL_ERROR;
302     }
303     /*
304      * Initiate the request for the selection. Note: can't use TkCurrentTime
305      * for the time. If we do, and this application hasn't received any X
306      * events in a long time, the current time will be way in the past and
307      * could even predate the time when the selection was made; if this
308      * happens, the request will be rejected.
309      */
310     Tcl_ThreadAlert(Tcl_GetCurrentThread());
311     /* Register an event handler for tkwin... */
312     Tk_CreateEventHandler(sel_tkwin, SelectionNotify,
313                           TkDND_SelectionNotifyEventProc, &detail);
314     XConvertSelection(display, selection, target,
315 	              selection, Tk_WindowId(sel_tkwin), time);
316     XFlush(display);
317     /*
318      * Enter a loop processing X events until the selection has been retrieved
319      * and processed. If no response is received within a few seconds, then
320      * timeout.
321      */
322     detail.timeout = Tcl_CreateTimerHandler(70, TkDND_SelTimeoutProc,
323 	                                    &detail);
324     while (detail.result == -1) {
325       TkDND_SelectionNotifyEventProc(&detail, NULL);
326       Tcl_DoOneEvent(0);
327     }
328     Tk_DeleteEventHandler(sel_tkwin, SelectionNotify,
329                           TkDND_SelectionNotifyEventProc, &detail);
330     if (detail.timeout) Tcl_DeleteTimerHandler(detail.timeout);
331 
332     return detail.result;
333 }
334 
335 /*
336  *----------------------------------------------------------------------
337  *
338  * TkDND_SelTimeoutProc --
339  *
340  *	This function is invoked once every second while waiting for the
341  *	selection to be returned. After a while it gives up and aborts the
342  *	selection retrieval.
343  *
344  * Results:
345  *	None.
346  *
347  * Side effects:
348  *	A new timer callback is created to call us again in another second,
349  *	unless time has expired, in which case an error is recorded for the
350  *	retrieval.
351  *
352  *----------------------------------------------------------------------
353  */
354 
355 static void
TkDND_SelTimeoutProc(ClientData clientData)356 TkDND_SelTimeoutProc(
357     ClientData clientData)	/* Information about retrieval in progress. */
358 {
359     register TkDND_ProcDetail *retrPtr = (TkDND_ProcDetail *) clientData;
360 
361     /*
362      * Make sure that the retrieval is still in progress. Then see how long
363      * it's been since any sort of response was received from the other side.
364      */
365 
366     TkDND_SelectionNotifyEventProc(retrPtr, NULL);
367     if (retrPtr->result != -1) {
368 	return;
369     }
370     XFlush(Tk_Display(retrPtr->tkwin));
371     if (retrPtr->idleTime > 3){
372       Tcl_ThreadAlert(Tcl_GetCurrentThread());
373       XFlush(Tk_Display(retrPtr->tkwin));
374     }
375     retrPtr->idleTime++;
376     if (retrPtr->idleTime >= 6) {
377 	/*
378 	 * Use a careful function to store the error message, because the
379 	 * result could already be partially filled in with a partial
380 	 * selection return.
381 	 */
382 
383 	Tcl_SetResult(retrPtr->interp, "selection owner didn't respond",
384 		TCL_STATIC);
385 	retrPtr->result = TCL_ERROR;
386         retrPtr->timeout = NULL;
387 
388     } else {
389 	retrPtr->timeout = Tcl_CreateTimerHandler(1000, TkDND_SelTimeoutProc,
390 		(ClientData) retrPtr);
391     }
392 }
393 
TkDND_GetSelection(Tcl_Interp * interp,Tk_Window tkwin,Atom selection,Atom target,Time time,Tk_GetSelProc * proc,ClientData clientData)394 int TkDND_GetSelection(Tcl_Interp *interp, Tk_Window tkwin, Atom selection,
395                        Atom target, Time time,
396                        Tk_GetSelProc *proc, ClientData clientData) {
397   /*
398    * The selection is owned by some other process.
399    */
400   return TkDNDSelGetSelection(interp, tkwin, selection, target, time,
401                               proc, clientData);
402 }
403 
404 /*
405  * Local Variables:
406  * mode: c
407  * c-basic-offset: 4
408  * fill-column: 78
409  * End:
410  */
411