ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/Unix/clip_unix.cpp
Revision: 1.5
Committed: 2004-01-01T11:28:22Z (20 years, 8 months ago) by gbeauche
Branch: MAIN
Changes since 1.4: +29 -1 lines
Log Message:
Add TODO and debug info for 'styl' resources, and TARGETS from requestors.

File Contents

# User Rev Content
1 cebix 1.1 /*
2     * clip_unix.cpp - Clipboard handling, Unix implementation
3     *
4 gbeauche 1.2 * SheepShaver (C) 1997-2003 Christian Bauer and Marc Hellwig
5 cebix 1.1 *
6     * This program is free software; you can redistribute it and/or modify
7     * it under the terms of the GNU General Public License as published by
8     * the Free Software Foundation; either version 2 of the License, or
9     * (at your option) any later version.
10     *
11     * This program is distributed in the hope that it will be useful,
12     * but WITHOUT ANY WARRANTY; without even the implied warranty of
13     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14     * GNU General Public License for more details.
15     *
16     * You should have received a copy of the GNU General Public License
17     * along with this program; if not, write to the Free Software
18     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19     */
20    
21 gbeauche 1.3 /*
22     * NOTES:
23     *
24     * We must have (fast) X11 display locking routines. Otherwise, we
25     * can corrupt the X11 event queue from the emulator thread whereas
26     * the redraw thread is expected to handle them.
27     *
28     * Two functions are exported to video_x.cpp:
29     * - ClipboardSelectionClear()
30     * called when we lose the selection ownership
31     * - ClipboardSelectionRequest()
32     * called when another client wants our clipboard data
33     * The display is locked by the redraw thread during their execution.
34     *
35     * On PutScrap (Mac application wrote to clipboard), we always cache
36     * the Mac clipboard to a local structure (clip_data). Then, the
37     * selection ownership is grabbed until ClipboardSelectionClear()
38     * occurs. In that case, contents in cache becomes invalid.
39     *
40     * On GetScrap (Mac application reads clipboard), we always fetch
41     * data from the X11 clipboard and immediately put it back to Mac
42     * side. Local cache does not need to be updated. If the selection
43     * owner supports the TIMESTAMP target, we can avoid useless copies.
44     *
45     * For safety purposes, we lock the X11 display in the emulator
46     * thread during the whole GetScrap/PutScrap execution. Of course, we
47     * temporarily release the lock when waiting for SelectioNotify.
48 gbeauche 1.5 *
49     * TODO:
50     * - handle 'PICT' to image/png, image/ppm, PIXMAP (prefs order)
51     * - handle 'styl' to text/richtext (OOo Writer)
52     * - patch ZeroScrap so that we know when cached 'styl' is stale?
53 gbeauche 1.3 */
54    
55 cebix 1.1 #include "sysdeps.h"
56    
57     #include <X11/Xlib.h>
58 gbeauche 1.2 #include <X11/Xatom.h>
59     #include <pthread.h>
60     #include <vector>
61 cebix 1.1
62     #include "macos_util.h"
63     #include "clip.h"
64 gbeauche 1.2 #include "prefs.h"
65     #include "cpu_emulation.h"
66     #include "main.h"
67     #include "emul_op.h"
68 cebix 1.1
69     #define DEBUG 0
70     #include "debug.h"
71    
72 gbeauche 1.2 #ifndef NO_STD_NAMESPACE
73     using std::vector;
74     #endif
75    
76    
77     // Do we replace PutScrap()?
78     #define REPLACE_PUTSCRAP 1
79    
80     // Do we replace GetScrap()?
81     #define REPLACE_GETSCRAP 1
82    
83     // Do we want GetScrap() to check for TIMESTAMP and optimize out clipboard syncs?
84     #define GETSCRAP_REQUESTS_TIMESTAMP 0
85    
86     // Do we want GetScrap() to first check for TARGETS available from the clipboard?
87     #define GETSCRAP_REQUESTS_TARGETS 0
88    
89 cebix 1.1
90     // From main_linux.cpp
91     extern Display *x_display;
92    
93    
94     // Conversion tables
95     static const uint8 mac2iso[0x80] = {
96     0xc4, 0xc5, 0xc7, 0xc9, 0xd1, 0xd6, 0xdc, 0xe1,
97     0xe0, 0xe2, 0xe4, 0xe3, 0xe5, 0xe7, 0xe9, 0xe8,
98     0xea, 0xeb, 0xed, 0xec, 0xee, 0xef, 0xf1, 0xf3,
99     0xf2, 0xf4, 0xf6, 0xf5, 0xfa, 0xf9, 0xfb, 0xfc,
100     0x2b, 0xb0, 0xa2, 0xa3, 0xa7, 0xb7, 0xb6, 0xdf,
101     0xae, 0xa9, 0x20, 0xb4, 0xa8, 0x23, 0xc6, 0xd8,
102     0x20, 0xb1, 0x3c, 0x3e, 0xa5, 0xb5, 0xf0, 0x53,
103     0x50, 0x70, 0x2f, 0xaa, 0xba, 0x4f, 0xe6, 0xf8,
104     0xbf, 0xa1, 0xac, 0x2f, 0x66, 0x7e, 0x44, 0xab,
105     0xbb, 0x2e, 0x20, 0xc0, 0xc3, 0xd5, 0x4f, 0x6f,
106     0x2d, 0x2d, 0x22, 0x22, 0x60, 0x27, 0xf7, 0x20,
107     0xff, 0x59, 0x2f, 0xa4, 0x3c, 0x3e, 0x66, 0x66,
108     0x23, 0xb7, 0x2c, 0x22, 0x25, 0xc2, 0xca, 0xc1,
109     0xcb, 0xc8, 0xcd, 0xce, 0xcf, 0xcc, 0xd3, 0xd4,
110     0x20, 0xd2, 0xda, 0xdb, 0xd9, 0x69, 0x5e, 0x7e,
111     0xaf, 0x20, 0xb7, 0xb0, 0xb8, 0x22, 0xb8, 0x20
112     };
113    
114 gbeauche 1.2 static const uint8 iso2mac[0x80] = {
115     0xad, 0xb0, 0xe2, 0xc4, 0xe3, 0xc9, 0xa0, 0xe0,
116     0xf6, 0xe4, 0xde, 0xdc, 0xce, 0xb2, 0xb3, 0xb6,
117     0xb7, 0xd4, 0xd5, 0xd2, 0xd3, 0xa5, 0xd0, 0xd1,
118     0xf7, 0xaa, 0xdf, 0xdd, 0xcf, 0xba, 0xfd, 0xd9,
119     0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xbd, 0xa4,
120     0xac, 0xa9, 0xbb, 0xc7, 0xc2, 0xf0, 0xa8, 0xf8,
121     0xa1, 0xb1, 0xc3, 0xc5, 0xab, 0xb5, 0xa6, 0xe1,
122     0xfc, 0xc6, 0xbc, 0xc8, 0xf9, 0xda, 0xd7, 0xc0,
123     0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82,
124     0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec,
125     0xf5, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xfb,
126     0xaf, 0xf4, 0xf2, 0xf3, 0x86, 0xfa, 0xb8, 0xa7,
127     0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d,
128     0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95,
129     0xfe, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6,
130     0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xff, 0xb9, 0xd8
131     };
132    
133     // Flag: Don't convert clipboard text
134     static bool no_clip_conversion;
135    
136     // Flag for PutScrap(): the data was put by GetScrap(), don't bounce it back to the Be side
137     static bool we_put_this_data = false;
138    
139     // X11 variables
140     static int screen; // Screen number
141     static Window rootwin, clip_win; // Root window and the clipboard window
142     static Atom xa_clipboard;
143     static Atom xa_targets;
144     static Atom xa_multiple;
145     static Atom xa_timestamp;
146     static Atom xa_atom_pair;
147    
148     // Define a byte array (rewrite if it's a bottleneck)
149     struct ByteArray : public vector<uint8> {
150     void resize(int size) { reserve(size); vector<uint8>::resize(size); }
151     uint8 *data() { return &at(0); }
152     };
153    
154 gbeauche 1.3 // Clipboard data for requestors
155 gbeauche 1.2 struct ClipboardData {
156     Time time;
157 gbeauche 1.3 Atom type;
158 gbeauche 1.2 ByteArray data;
159     };
160     static ClipboardData clip_data;
161    
162 gbeauche 1.3 // Prototypes
163     static void do_putscrap(uint32 type, void *scrap, int32 length);
164     static void do_getscrap(void **handle, uint32 type, int32 offset);
165    
166 gbeauche 1.2
167     /*
168     * Read an X11 property (hack from QT 3.1.2)
169     */
170    
171     static inline int max_selection_incr(Display *dpy)
172     {
173     int max_request_size = 4 * XMaxRequestSize(dpy);
174     if (max_request_size > 4 * 65536)
175     max_request_size = 4 * 65536;
176     else if ((max_request_size -= 100) < 0)
177     max_request_size = 100;
178     return max_request_size;
179     }
180    
181     static bool read_property(Display *dpy, Window win,
182     Atom property, bool deleteProperty,
183     ByteArray & buffer, int *size, Atom *type,
184     int *format, bool nullterm)
185     {
186     int maxsize = max_selection_incr(dpy);
187     unsigned long bytes_left;
188     unsigned long length;
189     unsigned char *data;
190     Atom dummy_type;
191     int dummy_format;
192    
193     if (!type)
194     type = &dummy_type;
195     if (!format)
196     format = &dummy_format;
197    
198     // Don't read anything but get the size of the property data
199     if (XGetWindowProperty(dpy, win, property, 0, 0, False,
200     AnyPropertyType, type, format, &length, &bytes_left, &data) != Success) {
201     buffer.clear();
202     return false;
203     }
204     XFree(data);
205    
206     int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
207    
208     switch (*format) {
209     case 16:
210     format_inc = sizeof(short) / 2;
211     proplen *= format_inc;
212     break;
213     case 32:
214     format_inc = sizeof(long) / 4;
215     proplen *= format_inc;
216     break;
217     }
218    
219     buffer.resize(proplen + (nullterm ? 1 : 0));
220     while (bytes_left) {
221     if (XGetWindowProperty(dpy, win, property, offset, maxsize / 4,
222     False, AnyPropertyType, type, format,
223     &length, &bytes_left, &data) != Success)
224     break;
225    
226     offset += length / (32 / *format);
227     length *= format_inc * (*format / 8);
228    
229     memcpy(buffer.data() + buffer_offset, data, length);
230     buffer_offset += length;
231     XFree(data);
232     }
233    
234     if (nullterm)
235     buffer[buffer_offset] = '\0';
236    
237     if (size)
238     *size = buffer_offset;
239    
240     if (deleteProperty)
241     XDeleteProperty(dpy, win, property);
242    
243     XFlush(dpy);
244     return true;
245     }
246    
247    
248     /*
249     * Timed wait for an SelectionNotify event
250     */
251    
252     static const uint64 SELECTION_MAX_WAIT = 500000; // 500 ms
253    
254 gbeauche 1.3 static bool wait_for_selection_notify_event(Display *dpy, Window win, XEvent *event, int timeout)
255 gbeauche 1.2 {
256     uint64 start = GetTicks_usec();
257    
258     do {
259     // Wait
260 gbeauche 1.3 XDisplayUnlock();
261     Delay_usec(5000);
262     XDisplayLock();
263 gbeauche 1.2
264 gbeauche 1.3 // Check for SelectionNotify event
265     if (XCheckTypedWindowEvent(dpy, win, SelectionNotify, event))
266 gbeauche 1.2 return true;
267    
268     } while ((GetTicks_usec() - start) < timeout);
269    
270     return false;
271     }
272    
273    
274     /*
275 cebix 1.1 * Initialization
276     */
277    
278     void ClipInit(void)
279     {
280 gbeauche 1.2 no_clip_conversion = PrefsFindBool("noclipconversion");
281    
282     // Find screen and root window
283     screen = XDefaultScreen(x_display);
284     rootwin = XRootWindow(x_display, screen);
285    
286     // Create fake window to receive selection events
287     clip_win = XCreateSimpleWindow(x_display, rootwin, 0, 0, 1, 1, 0, 0, 0);
288    
289     // Initialize X11 atoms
290     xa_clipboard = XInternAtom(x_display, "CLIPBOARD", False);
291     xa_targets = XInternAtom(x_display, "TARGETS", False);
292     xa_multiple = XInternAtom(x_display, "MULTIPLE", False);
293     xa_timestamp = XInternAtom(x_display, "TIMESTAMP", False);
294     xa_atom_pair = XInternAtom(x_display, "ATOM_PAIR", False);
295 cebix 1.1 }
296    
297    
298     /*
299     * Deinitialization
300     */
301    
302     void ClipExit(void)
303     {
304 gbeauche 1.2 // Close window
305     XDestroyWindow(x_display, clip_win);
306 cebix 1.1 }
307    
308    
309     /*
310     * Mac application wrote to clipboard
311     */
312    
313     void PutScrap(uint32 type, void *scrap, int32 length)
314     {
315     D(bug("PutScrap type %08lx, data %p, length %ld\n", type, scrap, length));
316 gbeauche 1.2 if (!REPLACE_PUTSCRAP)
317     return;
318     if (we_put_this_data) {
319     we_put_this_data = false;
320     return;
321     }
322 cebix 1.1 if (length <= 0)
323     return;
324    
325 gbeauche 1.3 XDisplayLock();
326     do_putscrap(type, scrap, length);
327     XDisplayUnlock();
328     }
329    
330     static void do_putscrap(uint32 type, void *scrap, int32 length)
331     {
332     clip_data.type = None;
333 cebix 1.1 switch (type) {
334 gbeauche 1.5 case FOURCC('T','E','X','T'): {
335 gbeauche 1.3 D(bug(" clipping TEXT\n"));
336     clip_data.type = XA_STRING;
337     clip_data.data.clear();
338     clip_data.data.reserve(length);
339    
340     // Convert text from Mac charset to ISO-Latin1
341     uint8 *p = (uint8 *)scrap;
342     for (int i=0; i<length; i++) {
343     uint8 c = *p++;
344     if (c < 0x80) {
345     if (c == 13) // CR -> LF
346     c = 10;
347     } else if (!no_clip_conversion)
348     c = mac2iso[c & 0x7f];
349     clip_data.data.push_back(c);
350     }
351     break;
352 gbeauche 1.2 }
353 cebix 1.1
354 gbeauche 1.5 case FOURCC('s','t','y','l'): {
355     D(bug(" clipping styl\n"));
356     uint16 *p = (uint16 *)scrap;
357     uint16 n = ntohs(*p++);
358     D(bug(" %d styles (%d bytes)\n", n, length));
359     for (int i = 0; i < n; i++) {
360     uint32 offset = ntohl(*(uint32 *)p); p += 2;
361     uint16 line_height = ntohs(*p++);
362     uint16 font_ascent = ntohs(*p++);
363     uint16 font_family = ntohs(*p++);
364     uint16 style_code = ntohs(*p++);
365     uint16 char_size = ntohs(*p++);
366     uint16 r = ntohs(*p++);
367     uint16 g = ntohs(*p++);
368     uint16 b = ntohs(*p++);
369     D(bug(" offset=%d, height=%d, font ascent=%d, id=%d, style=%x, size=%d, RGB=%x/%x/%x\n",
370     offset, line_height, font_ascent, font_family, style_code, char_size, r, g, b));
371     }
372     break;
373     }
374     }
375    
376 gbeauche 1.2 // Acquire selection ownership
377 gbeauche 1.3 if (clip_data.type != None) {
378 gbeauche 1.2 clip_data.time = CurrentTime;
379     while (XGetSelectionOwner(x_display, xa_clipboard) != clip_win)
380     XSetSelectionOwner(x_display, xa_clipboard, clip_win, clip_data.time);
381 cebix 1.1 }
382     }
383    
384    
385     /*
386     * Mac application reads clipboard
387     */
388    
389     void GetScrap(void **handle, uint32 type, int32 offset)
390     {
391     D(bug("GetScrap handle %p, type %08x, offset %d\n", handle, type, offset));
392 gbeauche 1.2 if (!REPLACE_GETSCRAP)
393     return;
394    
395 gbeauche 1.3 XDisplayLock();
396     do_getscrap(handle, type, offset);
397     XDisplayUnlock();
398     }
399    
400     static void do_getscrap(void **handle, uint32 type, int32 offset)
401     {
402     ByteArray data;
403     XEvent event;
404    
405     // If we own the selection, the data is already available on MacOS side
406     if (XGetSelectionOwner(x_display, xa_clipboard) == clip_win)
407     return;
408    
409 gbeauche 1.2 // Check TIMESTAMP
410     #if GETSCRAP_REQUESTS_TIMESTAMP
411     static Time last_timestamp = 0;
412     XConvertSelection(x_display, xa_clipboard, xa_timestamp, xa_clipboard, clip_win, CurrentTime);
413 gbeauche 1.3 if (wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) &&
414     event.xselection.property != None &&
415     read_property(x_display,
416     event.xselection.requestor, event.xselection.property,
417     true, data, 0, 0, 0, false)) {
418     Time timestamp = ((long *)data.data())[0];
419     if (timestamp <= last_timestamp)
420     return;
421 gbeauche 1.2 }
422 gbeauche 1.3 last_timestamp = CurrentTime;
423 gbeauche 1.2 #endif
424    
425     // Get TARGETS available
426     #if GETSCRAP_REQUESTS_TARGETS
427     XConvertSelection(x_display, xa_clipboard, xa_targets, xa_clipboard, clip_win, CurrentTime);
428 gbeauche 1.3 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
429     event.xselection.property == None ||
430     !read_property(x_display,
431     event.xselection.requestor, event.xselection.property,
432 gbeauche 1.2 true, data, 0, 0, 0, false))
433     return;
434     #endif
435    
436     // Get appropriate format for requested data
437     Atom format = None;
438     #if GETSCRAP_REQUESTS_TARGETS
439     int n_atoms = data.size() / sizeof(long);
440     long *atoms = (long *)data.data();
441     for (int i = 0; i < n_atoms; i++) {
442     Atom target = atoms[i];
443 gbeauche 1.5 D(bug(" target %08x (%s)\n", target, XGetAtomName(x_display, target)));
444 gbeauche 1.2 switch (type) {
445     case FOURCC('T','E','X','T'):
446     D(bug(" clipping TEXT\n"));
447     if (target == XA_STRING)
448     format = target;
449     break;
450     case FOURCC('P','I','C','T'):
451     break;
452     }
453     }
454     #else
455 cebix 1.1 switch (type) {
456 gbeauche 1.2 case FOURCC('T','E','X','T'):
457     D(bug(" clipping TEXT\n"));
458     format = XA_STRING;
459     break;
460     case FOURCC('P','I','C','T'):
461     break;
462     }
463     #endif
464     if (format == None)
465     return;
466    
467     // Get the native clipboard data
468     XConvertSelection(x_display, xa_clipboard, format, xa_clipboard, clip_win, CurrentTime);
469 gbeauche 1.3 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
470     event.xselection.property == None ||
471 gbeauche 1.2 !read_property(x_display,
472 gbeauche 1.3 event.xselection.requestor, event.xselection.property,
473     false, data, 0, 0, 0, format == XA_STRING))
474 gbeauche 1.2 return;
475    
476     // Allocate space for new scrap in MacOS side
477     M68kRegisters r;
478 gbeauche 1.3 r.d[0] = data.size();
479 gbeauche 1.2 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
480     uint32 scrap_area = r.a[0];
481    
482     if (scrap_area) {
483     switch (type) {
484 cebix 1.1 case FOURCC('T','E','X','T'):
485 gbeauche 1.2 // Convert text from ISO-Latin1 to Mac charset
486     uint8 *p = Mac2HostAddr(scrap_area);
487 gbeauche 1.3 for (int i = 0; i < data.size(); i++) {
488     uint8 c = data[i];
489 gbeauche 1.2 if (c < 0x80) {
490     if (c == 10) // LF -> CR
491     c = 13;
492     } else if (!no_clip_conversion)
493     c = iso2mac[c & 0x7f];
494     *p++ = c;
495     }
496 cebix 1.1 break;
497 gbeauche 1.2 }
498    
499     // Add new data to clipboard
500     static uint8 proc[] = {
501     0x59, 0x8f, // subq.l #4,sp
502     0xa9, 0xfc, // ZeroScrap()
503     0x2f, 0x3c, 0, 0, 0, 0, // move.l #length,-(sp)
504     0x2f, 0x3c, 0, 0, 0, 0, // move.l #type,-(sp)
505     0x2f, 0x3c, 0, 0, 0, 0, // move.l #outbuf,-(sp)
506     0xa9, 0xfe, // PutScrap()
507     0x58, 0x8f, // addq.l #4,sp
508     M68K_RTS >> 8, M68K_RTS
509     };
510     uint32 proc_area = (uint32)proc; // FIXME: make sure 32-bit relocs are used
511 gbeauche 1.3 WriteMacInt32(proc_area + 6, data.size());
512 gbeauche 1.2 WriteMacInt32(proc_area + 12, type);
513     WriteMacInt32(proc_area + 18, scrap_area);
514     we_put_this_data = true;
515     Execute68k(proc_area, &r);
516    
517     // We are done with scratch memory
518     r.a[0] = scrap_area;
519     Execute68kTrap(0xa01f, &r); // DisposePtr
520 cebix 1.1 }
521 gbeauche 1.2 }
522    
523    
524     /*
525     * Handle X11 selection events
526     */
527    
528     void ClipboardSelectionClear(XSelectionClearEvent *xev)
529     {
530     if (xev->selection != xa_clipboard)
531     return;
532    
533     D(bug("Selection cleared, lost clipboard ownership\n"));
534 gbeauche 1.3 clip_data.type = None;
535 gbeauche 1.2 clip_data.data.clear();
536     }
537    
538     // Top level selection handler
539     static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple);
540    
541     static bool handle_selection_TIMESTAMP(XSelectionRequestEvent *req)
542     {
543     // 32-bit integer values are always passed as a long whatever is its size
544     long timestamp = clip_data.time;
545    
546     // Change requestor property
547     XChangeProperty(x_display, req->requestor, req->property,
548     XA_INTEGER, 32,
549     PropModeReplace, (uint8 *)&timestamp, 1);
550    
551     return true;
552     }
553    
554     static bool handle_selection_TARGETS(XSelectionRequestEvent *req)
555     {
556     // Default supported targets
557     vector<long> targets;
558     targets.push_back(xa_targets);
559     targets.push_back(xa_multiple);
560     targets.push_back(xa_timestamp);
561    
562     // Extra targets matchin current clipboard data
563 gbeauche 1.3 if (clip_data.type != None)
564     targets.push_back(clip_data.type);
565 gbeauche 1.2
566     // Change requestor property
567     XChangeProperty(x_display, req->requestor, req->property,
568     xa_targets, 32,
569     PropModeReplace, (uint8 *)&targets.at(0), targets.size());
570    
571     return true;
572     }
573    
574     static bool handle_selection_STRING(XSelectionRequestEvent *req)
575     {
576     // Make sure we have valid data to send though ICCCM compliant
577     // clients should have first requested TARGETS to identify
578     // this possibility.
579 gbeauche 1.3 if (clip_data.type != XA_STRING)
580 gbeauche 1.2 return false;
581    
582     // Send the string, it's already encoded as ISO-8859-1
583     XChangeProperty(x_display, req->requestor, req->property,
584     XA_STRING, 8,
585     PropModeReplace, (uint8 *)clip_data.data.data(), clip_data.data.size());
586    
587     return true;
588     }
589    
590     static bool handle_selection_MULTIPLE(XSelectionRequestEvent *req)
591     {
592     Atom rtype;
593     int rformat;
594     ByteArray data;
595    
596     if (!read_property(x_display, req->requestor, req->property,
597     false, data, 0, &rtype, &rformat, 0))
598     return false;
599    
600     // rtype should be ATOM_PAIR but some clients don't honour that
601     if (rformat != 32)
602     return false;
603    
604     struct AtomPair { long target; long property; };
605     AtomPair *atom_pairs = (AtomPair *)data.data();
606     int n_atom_pairs = data.size() / sizeof(AtomPair);
607    
608     bool handled = true;
609     if (n_atom_pairs) {
610     // Setup a new XSelectionRequestEvent when servicing individual requests
611     XSelectionRequestEvent event;
612     memcpy(&event, req, sizeof(event));
613    
614     for (int i = 0; i < n_atom_pairs; i++) {
615     Atom target = atom_pairs[i].target;
616     Atom property = atom_pairs[i].property;
617    
618     // None is not a valid property
619     if (property == None)
620     continue;
621    
622     // Service this request
623     event.target = target;
624     event.property = property;
625     if (!handle_selection(&event, true)) {
626     /* FIXME: ICCCM 2.6.2:
627    
628     If the owner fails to convert the target named by an
629     atom in the MULTIPLE property, it should replace that
630     atom in the property with None.
631     */
632     handled = false;
633     }
634     }
635     }
636    
637     return handled;
638     }
639    
640     static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple)
641     {
642     bool handled =false;
643    
644     if (req->target == xa_timestamp)
645     handled = handle_selection_TIMESTAMP(req);
646     else if (req->target == xa_targets)
647     handled = handle_selection_TARGETS(req);
648     else if (req->target == XA_STRING)
649     handled = handle_selection_STRING(req);
650     else if (req->target == xa_multiple)
651     handled = handle_selection_MULTIPLE(req);
652    
653     // Notify requestor only when we are done with his request
654     if (handled && !is_multiple) {
655     XEvent out_event;
656     out_event.xselection.type = SelectionNotify;
657     out_event.xselection.requestor = req->requestor;
658     out_event.xselection.selection = req->selection;
659     out_event.xselection.target = req->target;
660     out_event.xselection.property = handled ? req->property : None;
661     out_event.xselection.time = req->time;
662     XSendEvent(x_display, req->requestor, False, 0, &out_event);
663     }
664     }
665    
666     void ClipboardSelectionRequest(XSelectionRequestEvent *req)
667     {
668     if (req->requestor == clip_win || req->selection != xa_clipboard)
669     return;
670    
671     D(bug("Selection requested from 0x%lx to 0x%lx (%s) 0x%lx (%s)\n",
672     req->requestor,
673     req->selection,
674     XGetAtomName(req->display, req->selection),
675     req->target,
676     XGetAtomName(req->display, req->target)));
677    
678     handle_selection(req, false);
679 cebix 1.1 }