ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/Unix/clip_unix.cpp
Revision: 1.3
Committed: 2003-12-31T18:16:55Z (20 years, 6 months ago) by gbeauche
Branch: MAIN
Changes since 1.2: +107 -109 lines
Log Message:
Add fast X11 display locking routines based on spinlocks, or on pthreads
in the worst case. Optimize out GetScrap() case when we already own the
selection. i.e. make it smoother. Use our own XDisplay{Un,}Lock() routines.

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