ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/Unix/clip_unix.cpp
Revision: 1.10
Committed: 2004-11-15T23:27:43Z (20 years ago) by gbeauche
Branch: MAIN
Changes since 1.9: +584 -18 lines
Log Message:
Backport copy-paste of 'TEXT' from SheepShaver

File Contents

# Content
1 /*
2 * clip_unix.cpp - Clipboard handling, Unix implementation
3 *
4 * SheepShaver (C) 1997-2004 Christian Bauer and Marc Hellwig
5 *
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 /*
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 * 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 */
54
55 #include "sysdeps.h"
56
57 #include <X11/Xlib.h>
58 #include <X11/Xatom.h>
59 #include <pthread.h>
60 #include <vector>
61
62 #include "macos_util.h"
63 #include "clip.h"
64 #include "prefs.h"
65 #include "cpu_emulation.h"
66 #include "main.h"
67 #include "emul_op.h"
68
69 #define DEBUG 0
70 #include "debug.h"
71
72 #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
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 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 Unix 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 &(*this)[0]; }
146 };
147
148 // Clipboard data for requestors
149 struct ClipboardData {
150 Time time;
151 Atom type;
152 ByteArray data;
153 };
154 static ClipboardData clip_data;
155
156 // 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
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 static bool wait_for_selection_notify_event(Display *dpy, Window win, XEvent *event, int timeout)
249 {
250 uint64 start = GetTicks_usec();
251
252 do {
253 // Wait
254 XDisplayUnlock();
255 Delay_usec(5000);
256 XDisplayLock();
257
258 // Check for SelectionNotify event
259 if (XCheckTypedWindowEvent(dpy, win, SelectionNotify, event))
260 return true;
261
262 } while ((GetTicks_usec() - start) < timeout);
263
264 return false;
265 }
266
267
268 /*
269 * Initialization
270 */
271
272 void ClipInit(void)
273 {
274 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 }
290
291
292 /*
293 * Deinitialization
294 */
295
296 void ClipExit(void)
297 {
298 // Close window
299 XDestroyWindow(x_display, clip_win);
300 }
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 if (we_put_this_data) {
311 we_put_this_data = false;
312 return;
313 }
314 if (length <= 0)
315 return;
316
317 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 switch (type) {
326 case FOURCC('T','E','X','T'): {
327 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 }
345
346 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 // Acquire selection ownership
369 if (clip_data.type != None) {
370 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 }
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
385 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 // 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 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 }
412 last_timestamp = CurrentTime;
413 #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 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 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 D(bug(" target %08x (%s)\n", target, XGetAtomName(x_display, target)));
434 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 switch (type) {
446 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 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
460 event.xselection.property == None ||
461 !read_property(x_display,
462 event.xselection.requestor, event.xselection.property,
463 false, data, 0, 0, 0, format == XA_STRING))
464 return;
465
466 // Allocate space for new scrap in MacOS side
467 M68kRegisters r;
468 r.d[0] = data.size();
469 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
470 uint32 scrap_area = r.a[0];
471
472 if (scrap_area) {
473 switch (type) {
474 case FOURCC('T','E','X','T'):
475 // Convert text from ISO-Latin1 to Mac charset
476 uint8 *p = Mac2HostAddr(scrap_area);
477 for (int i = 0; i < data.size(); i++) {
478 uint8 c = data[i];
479 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 break;
487 }
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 r.d[0] = sizeof(proc);
501 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
502 uint32 proc_area = r.a[0];
503
504 // The procedure is run-time generated because it must lays in
505 // Mac address space. This is mandatory for "33-bit" address
506 // space optimization on 64-bit platforms because the static
507 // proc[] array is not remapped
508 Host2Mac_memcpy(proc_area, proc, sizeof(proc));
509 WriteMacInt32(proc_area + 6, data.size());
510 WriteMacInt32(proc_area + 12, type);
511 WriteMacInt32(proc_area + 18, scrap_area);
512 we_put_this_data = true;
513 Execute68k(proc_area, &r);
514
515 // We are done with scratch memory
516 r.a[0] = proc_area;
517 Execute68kTrap(0xa01f, &r); // DisposePtr
518 r.a[0] = scrap_area;
519 Execute68kTrap(0xa01f, &r); // DisposePtr
520 }
521 }
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 clip_data.type = None;
535 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 if (clip_data.type != None)
564 targets.push_back(clip_data.type);
565
566 // Change requestor property
567 XChangeProperty(x_display, req->requestor, req->property,
568 xa_targets, 32,
569 PropModeReplace, (uint8 *)&targets[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 if (clip_data.type != XA_STRING)
580 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 }