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