ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/Unix/clip_unix.cpp
Revision: 1.6
Committed: 2004-01-01T11:29:38Z (20 years, 10 months ago) by gbeauche
Branch: MAIN
Changes since 1.5: +0 -10 lines
Log Message:
PutScrap and GetScrap replacements are always enabled.

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