ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/Unix/clip_unix.cpp
Revision: 1.12
Committed: 2004-11-15T23:41:08Z (20 years ago) by gbeauche
Branch: MAIN
CVS Tags: HEAD
Changes since 1.11: +0 -0 lines
State: FILE REMOVED
Error occurred while calculating annotation data.
Log Message:
Inherit clip_unix.cpp from Basilisk II, now they are identical.

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 uint16 proc[] = {
491 PW(0x598f), // subq.l #4,sp
492 PW(0xa9fc), // ZeroScrap()
493 PW(0x2f3c), 0, 0, // move.l #length,-(sp)
494 PW(0x2f3c), 0, 0, // move.l #type,-(sp)
495 PW(0x2f3c), 0, 0, // move.l #outbuf,-(sp)
496 PW(0xa9fe), // PutScrap()
497 PW(0x588f), // addq.l #4,sp
498 PW(M68K_RTS)
499 };
500 uint32 proc_area = Host2MacAddr((uint8 *)proc);
501 WriteMacInt32(proc_area + 6, data.size());
502 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 }
511 }
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 clip_data.type = None;
525 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 if (clip_data.type != None)
554 targets.push_back(clip_data.type);
555
556 // Change requestor property
557 XChangeProperty(x_display, req->requestor, req->property,
558 xa_targets, 32,
559 PropModeReplace, (uint8 *)&targets[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 if (clip_data.type != XA_STRING)
570 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 }