ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/Unix/clip_unix.cpp
Revision: 1.15
Committed: 2010-02-21T09:55:52Z (14 years, 2 months ago) by cebix
Branch: MAIN
CVS Tags: HEAD
Changes since 1.14: +2 -2 lines
Log Message:
fixed compiler warnings

File Contents

# Content
1 /*
2 * clip_unix.cpp - Clipboard handling, Unix implementation
3 *
4 * SheepShaver (C) 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 a 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 if (clip_win)
300 XDestroyWindow(x_display, clip_win);
301 }
302
303
304 /*
305 * Mac application wrote to clipboard
306 */
307
308 void PutScrap(uint32 type, void *scrap, int32 length)
309 {
310 D(bug("PutScrap type %08lx, data %p, length %ld\n", type, scrap, length));
311 if (we_put_this_data) {
312 we_put_this_data = false;
313 return;
314 }
315 if (length <= 0)
316 return;
317
318 XDisplayLock();
319 do_putscrap(type, scrap, length);
320 XDisplayUnlock();
321 }
322
323 static void do_putscrap(uint32 type, void *scrap, int32 length)
324 {
325 clip_data.type = None;
326 switch (type) {
327 case FOURCC('T','E','X','T'): {
328 D(bug(" clipping TEXT\n"));
329 clip_data.type = XA_STRING;
330 clip_data.data.clear();
331 clip_data.data.reserve(length);
332
333 // Convert text from Mac charset to ISO-Latin1
334 uint8 *p = (uint8 *)scrap;
335 for (int i=0; i<length; i++) {
336 uint8 c = *p++;
337 if (c < 0x80) {
338 if (c == 13) // CR -> LF
339 c = 10;
340 } else if (!no_clip_conversion)
341 c = mac2iso[c & 0x7f];
342 clip_data.data.push_back(c);
343 }
344 break;
345 }
346
347 case FOURCC('s','t','y','l'): {
348 D(bug(" clipping styl\n"));
349 uint16 *p = (uint16 *)scrap;
350 uint16 n = ntohs(*p++);
351 D(bug(" %d styles (%d bytes)\n", n, length));
352 for (int i = 0; i < n; i++) {
353 uint32 offset = ntohl(*(uint32 *)p); p += 2;
354 uint16 line_height = ntohs(*p++);
355 uint16 font_ascent = ntohs(*p++);
356 uint16 font_family = ntohs(*p++);
357 uint16 style_code = ntohs(*p++);
358 uint16 char_size = ntohs(*p++);
359 uint16 r = ntohs(*p++);
360 uint16 g = ntohs(*p++);
361 uint16 b = ntohs(*p++);
362 D(bug(" offset=%d, height=%d, font ascent=%d, id=%d, style=%x, size=%d, RGB=%x/%x/%x\n",
363 offset, line_height, font_ascent, font_family, style_code, char_size, r, g, b));
364 }
365 break;
366 }
367 }
368
369 // Acquire selection ownership
370 if (clip_data.type != None) {
371 clip_data.time = CurrentTime;
372 while (XGetSelectionOwner(x_display, xa_clipboard) != clip_win)
373 XSetSelectionOwner(x_display, xa_clipboard, clip_win, clip_data.time);
374 }
375 }
376
377
378 /*
379 * Mac application reads clipboard
380 */
381
382 void GetScrap(void **handle, uint32 type, int32 offset)
383 {
384 D(bug("GetScrap handle %p, type %08x, offset %d\n", handle, type, offset));
385
386 XDisplayLock();
387 do_getscrap(handle, type, offset);
388 XDisplayUnlock();
389 }
390
391 static void do_getscrap(void **handle, uint32 type, int32 offset)
392 {
393 ByteArray data;
394 XEvent event;
395
396 // If we own the selection, the data is already available on MacOS side
397 if (XGetSelectionOwner(x_display, xa_clipboard) == clip_win)
398 return;
399
400 // Check TIMESTAMP
401 #if GETSCRAP_REQUESTS_TIMESTAMP
402 static Time last_timestamp = 0;
403 XConvertSelection(x_display, xa_clipboard, xa_timestamp, xa_clipboard, clip_win, CurrentTime);
404 if (wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) &&
405 event.xselection.property != None &&
406 read_property(x_display,
407 event.xselection.requestor, event.xselection.property,
408 true, data, 0, 0, 0, false)) {
409 Time timestamp = ((long *)data.data())[0];
410 if (timestamp <= last_timestamp)
411 return;
412 }
413 last_timestamp = CurrentTime;
414 #endif
415
416 // Get TARGETS available
417 #if GETSCRAP_REQUESTS_TARGETS
418 XConvertSelection(x_display, xa_clipboard, xa_targets, xa_clipboard, clip_win, CurrentTime);
419 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
420 event.xselection.property == None ||
421 !read_property(x_display,
422 event.xselection.requestor, event.xselection.property,
423 true, data, 0, 0, 0, false))
424 return;
425 #endif
426
427 // Get appropriate format for requested data
428 Atom format = None;
429 #if GETSCRAP_REQUESTS_TARGETS
430 int n_atoms = data.size() / sizeof(long);
431 long *atoms = (long *)data.data();
432 for (int i = 0; i < n_atoms; i++) {
433 Atom target = atoms[i];
434 D(bug(" target %08x (%s)\n", target, XGetAtomName(x_display, target)));
435 switch (type) {
436 case FOURCC('T','E','X','T'):
437 D(bug(" clipping TEXT\n"));
438 if (target == XA_STRING)
439 format = target;
440 break;
441 case FOURCC('P','I','C','T'):
442 break;
443 }
444 }
445 #else
446 switch (type) {
447 case FOURCC('T','E','X','T'):
448 D(bug(" clipping TEXT\n"));
449 format = XA_STRING;
450 break;
451 case FOURCC('P','I','C','T'):
452 break;
453 }
454 #endif
455 if (format == None)
456 return;
457
458 // Get the native clipboard data
459 XConvertSelection(x_display, xa_clipboard, format, xa_clipboard, clip_win, CurrentTime);
460 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
461 event.xselection.property == None ||
462 !read_property(x_display,
463 event.xselection.requestor, event.xselection.property,
464 false, data, 0, 0, 0, format == XA_STRING))
465 return;
466
467 // Allocate space for new scrap in MacOS side
468 M68kRegisters r;
469 r.d[0] = data.size();
470 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
471 uint32 scrap_area = r.a[0];
472
473 if (scrap_area) {
474 switch (type) {
475 case FOURCC('T','E','X','T'):
476 // Convert text from ISO-Latin1 to Mac charset
477 uint8 *p = Mac2HostAddr(scrap_area);
478 for (int i = 0; i < data.size(); i++) {
479 uint8 c = data[i];
480 if (c < 0x80) {
481 if (c == 10) // LF -> CR
482 c = 13;
483 } else if (!no_clip_conversion)
484 c = iso2mac[c & 0x7f];
485 *p++ = c;
486 }
487 break;
488 }
489
490 // Add new data to clipboard
491 static uint8 proc[] = {
492 0x59, 0x8f, // subq.l #4,sp
493 0xa9, 0xfc, // ZeroScrap()
494 0x2f, 0x3c, 0, 0, 0, 0, // move.l #length,-(sp)
495 0x2f, 0x3c, 0, 0, 0, 0, // move.l #type,-(sp)
496 0x2f, 0x3c, 0, 0, 0, 0, // move.l #outbuf,-(sp)
497 0xa9, 0xfe, // PutScrap()
498 0x58, 0x8f, // addq.l #4,sp
499 M68K_RTS >> 8, M68K_RTS & 0xff
500 };
501 r.d[0] = sizeof(proc);
502 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
503 uint32 proc_area = r.a[0];
504
505 // The procedure is run-time generated because it must lays in
506 // Mac address space. This is mandatory for "33-bit" address
507 // space optimization on 64-bit platforms because the static
508 // proc[] array is not remapped
509 Host2Mac_memcpy(proc_area, proc, sizeof(proc));
510 WriteMacInt32(proc_area + 6, data.size());
511 WriteMacInt32(proc_area + 12, type);
512 WriteMacInt32(proc_area + 18, scrap_area);
513 we_put_this_data = true;
514 Execute68k(proc_area, &r);
515
516 // We are done with scratch memory
517 r.a[0] = proc_area;
518 Execute68kTrap(0xa01f, &r); // DisposePtr
519 r.a[0] = scrap_area;
520 Execute68kTrap(0xa01f, &r); // DisposePtr
521 }
522 }
523
524
525 /*
526 * Handle X11 selection events
527 */
528
529 void ClipboardSelectionClear(XSelectionClearEvent *xev)
530 {
531 if (xev->selection != xa_clipboard)
532 return;
533
534 D(bug("Selection cleared, lost clipboard ownership\n"));
535 clip_data.type = None;
536 clip_data.data.clear();
537 }
538
539 // Top level selection handler
540 static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple);
541
542 static bool handle_selection_TIMESTAMP(XSelectionRequestEvent *req)
543 {
544 // 32-bit integer values are always passed as a long whatever is its size
545 long timestamp = clip_data.time;
546
547 // Change requestor property
548 XChangeProperty(x_display, req->requestor, req->property,
549 XA_INTEGER, 32,
550 PropModeReplace, (uint8 *)&timestamp, 1);
551
552 return true;
553 }
554
555 static bool handle_selection_TARGETS(XSelectionRequestEvent *req)
556 {
557 // Default supported targets
558 vector<long> targets;
559 targets.push_back(xa_targets);
560 targets.push_back(xa_multiple);
561 targets.push_back(xa_timestamp);
562
563 // Extra targets matchin current clipboard data
564 if (clip_data.type != None)
565 targets.push_back(clip_data.type);
566
567 // Change requestor property
568 XChangeProperty(x_display, req->requestor, req->property,
569 xa_targets, 32,
570 PropModeReplace, (uint8 *)&targets[0], targets.size());
571
572 return true;
573 }
574
575 static bool handle_selection_STRING(XSelectionRequestEvent *req)
576 {
577 // Make sure we have valid data to send though ICCCM compliant
578 // clients should have first requested TARGETS to identify
579 // this possibility.
580 if (clip_data.type != XA_STRING)
581 return false;
582
583 // Send the string, it's already encoded as ISO-8859-1
584 XChangeProperty(x_display, req->requestor, req->property,
585 XA_STRING, 8,
586 PropModeReplace, (uint8 *)clip_data.data.data(), clip_data.data.size());
587
588 return true;
589 }
590
591 static bool handle_selection_MULTIPLE(XSelectionRequestEvent *req)
592 {
593 Atom rtype;
594 int rformat;
595 ByteArray data;
596
597 if (!read_property(x_display, req->requestor, req->property,
598 false, data, 0, &rtype, &rformat, 0))
599 return false;
600
601 // rtype should be ATOM_PAIR but some clients don't honour that
602 if (rformat != 32)
603 return false;
604
605 struct AtomPair { long target; long property; };
606 AtomPair *atom_pairs = (AtomPair *)data.data();
607 int n_atom_pairs = data.size() / sizeof(AtomPair);
608
609 bool handled = true;
610 if (n_atom_pairs) {
611 // Setup a new XSelectionRequestEvent when servicing individual requests
612 XSelectionRequestEvent event;
613 memcpy(&event, req, sizeof(event));
614
615 for (int i = 0; i < n_atom_pairs; i++) {
616 Atom target = atom_pairs[i].target;
617 Atom property = atom_pairs[i].property;
618
619 // None is not a valid property
620 if (property == None)
621 continue;
622
623 // Service this request
624 event.target = target;
625 event.property = property;
626 if (!handle_selection(&event, true)) {
627 /* FIXME: ICCCM 2.6.2:
628
629 If the owner fails to convert the target named by an
630 atom in the MULTIPLE property, it should replace that
631 atom in the property with None.
632 */
633 handled = false;
634 }
635 }
636 }
637
638 return handled;
639 }
640
641 static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple)
642 {
643 bool handled =false;
644
645 if (req->target == xa_timestamp)
646 handled = handle_selection_TIMESTAMP(req);
647 else if (req->target == xa_targets)
648 handled = handle_selection_TARGETS(req);
649 else if (req->target == XA_STRING)
650 handled = handle_selection_STRING(req);
651 else if (req->target == xa_multiple)
652 handled = handle_selection_MULTIPLE(req);
653
654 // Notify requestor only when we are done with his request
655 if (handled && !is_multiple) {
656 XEvent out_event;
657 out_event.xselection.type = SelectionNotify;
658 out_event.xselection.requestor = req->requestor;
659 out_event.xselection.selection = req->selection;
660 out_event.xselection.target = req->target;
661 out_event.xselection.property = handled ? req->property : None;
662 out_event.xselection.time = req->time;
663 XSendEvent(x_display, req->requestor, False, 0, &out_event);
664 }
665
666 return handled;
667 }
668
669 void ClipboardSelectionRequest(XSelectionRequestEvent *req)
670 {
671 if (req->requestor == clip_win || req->selection != xa_clipboard)
672 return;
673
674 D(bug("Selection requested from 0x%lx to 0x%lx (%s) 0x%lx (%s)\n",
675 req->requestor,
676 req->selection,
677 XGetAtomName(req->display, req->selection),
678 req->target,
679 XGetAtomName(req->display, req->target)));
680
681 handle_selection(req, false);
682 }