ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/Unix/clip_unix.cpp
Revision: 1.5
Committed: 2004-01-01T11:28:22Z (20 years, 6 months ago) by gbeauche
Branch: MAIN
Changes since 1.4: +29 -1 lines
Log Message:
Add TODO and debug info for 'styl' resources, and TARGETS from requestors.

File Contents

# Content
1 /*
2 * clip_unix.cpp - Clipboard handling, Unix implementation
3 *
4 * SheepShaver (C) 1997-2003 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 replace PutScrap()?
78 #define REPLACE_PUTSCRAP 1
79
80 // Do we replace GetScrap()?
81 #define REPLACE_GETSCRAP 1
82
83 // Do we want GetScrap() to check for TIMESTAMP and optimize out clipboard syncs?
84 #define GETSCRAP_REQUESTS_TIMESTAMP 0
85
86 // Do we want GetScrap() to first check for TARGETS available from the clipboard?
87 #define GETSCRAP_REQUESTS_TARGETS 0
88
89
90 // From main_linux.cpp
91 extern Display *x_display;
92
93
94 // Conversion tables
95 static const uint8 mac2iso[0x80] = {
96 0xc4, 0xc5, 0xc7, 0xc9, 0xd1, 0xd6, 0xdc, 0xe1,
97 0xe0, 0xe2, 0xe4, 0xe3, 0xe5, 0xe7, 0xe9, 0xe8,
98 0xea, 0xeb, 0xed, 0xec, 0xee, 0xef, 0xf1, 0xf3,
99 0xf2, 0xf4, 0xf6, 0xf5, 0xfa, 0xf9, 0xfb, 0xfc,
100 0x2b, 0xb0, 0xa2, 0xa3, 0xa7, 0xb7, 0xb6, 0xdf,
101 0xae, 0xa9, 0x20, 0xb4, 0xa8, 0x23, 0xc6, 0xd8,
102 0x20, 0xb1, 0x3c, 0x3e, 0xa5, 0xb5, 0xf0, 0x53,
103 0x50, 0x70, 0x2f, 0xaa, 0xba, 0x4f, 0xe6, 0xf8,
104 0xbf, 0xa1, 0xac, 0x2f, 0x66, 0x7e, 0x44, 0xab,
105 0xbb, 0x2e, 0x20, 0xc0, 0xc3, 0xd5, 0x4f, 0x6f,
106 0x2d, 0x2d, 0x22, 0x22, 0x60, 0x27, 0xf7, 0x20,
107 0xff, 0x59, 0x2f, 0xa4, 0x3c, 0x3e, 0x66, 0x66,
108 0x23, 0xb7, 0x2c, 0x22, 0x25, 0xc2, 0xca, 0xc1,
109 0xcb, 0xc8, 0xcd, 0xce, 0xcf, 0xcc, 0xd3, 0xd4,
110 0x20, 0xd2, 0xda, 0xdb, 0xd9, 0x69, 0x5e, 0x7e,
111 0xaf, 0x20, 0xb7, 0xb0, 0xb8, 0x22, 0xb8, 0x20
112 };
113
114 static const uint8 iso2mac[0x80] = {
115 0xad, 0xb0, 0xe2, 0xc4, 0xe3, 0xc9, 0xa0, 0xe0,
116 0xf6, 0xe4, 0xde, 0xdc, 0xce, 0xb2, 0xb3, 0xb6,
117 0xb7, 0xd4, 0xd5, 0xd2, 0xd3, 0xa5, 0xd0, 0xd1,
118 0xf7, 0xaa, 0xdf, 0xdd, 0xcf, 0xba, 0xfd, 0xd9,
119 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xbd, 0xa4,
120 0xac, 0xa9, 0xbb, 0xc7, 0xc2, 0xf0, 0xa8, 0xf8,
121 0xa1, 0xb1, 0xc3, 0xc5, 0xab, 0xb5, 0xa6, 0xe1,
122 0xfc, 0xc6, 0xbc, 0xc8, 0xf9, 0xda, 0xd7, 0xc0,
123 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82,
124 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec,
125 0xf5, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xfb,
126 0xaf, 0xf4, 0xf2, 0xf3, 0x86, 0xfa, 0xb8, 0xa7,
127 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d,
128 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95,
129 0xfe, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6,
130 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xff, 0xb9, 0xd8
131 };
132
133 // Flag: Don't convert clipboard text
134 static bool no_clip_conversion;
135
136 // Flag for PutScrap(): the data was put by GetScrap(), don't bounce it back to the Be side
137 static bool we_put_this_data = false;
138
139 // X11 variables
140 static int screen; // Screen number
141 static Window rootwin, clip_win; // Root window and the clipboard window
142 static Atom xa_clipboard;
143 static Atom xa_targets;
144 static Atom xa_multiple;
145 static Atom xa_timestamp;
146 static Atom xa_atom_pair;
147
148 // Define a byte array (rewrite if it's a bottleneck)
149 struct ByteArray : public vector<uint8> {
150 void resize(int size) { reserve(size); vector<uint8>::resize(size); }
151 uint8 *data() { return &at(0); }
152 };
153
154 // Clipboard data for requestors
155 struct ClipboardData {
156 Time time;
157 Atom type;
158 ByteArray data;
159 };
160 static ClipboardData clip_data;
161
162 // Prototypes
163 static void do_putscrap(uint32 type, void *scrap, int32 length);
164 static void do_getscrap(void **handle, uint32 type, int32 offset);
165
166
167 /*
168 * Read an X11 property (hack from QT 3.1.2)
169 */
170
171 static inline int max_selection_incr(Display *dpy)
172 {
173 int max_request_size = 4 * XMaxRequestSize(dpy);
174 if (max_request_size > 4 * 65536)
175 max_request_size = 4 * 65536;
176 else if ((max_request_size -= 100) < 0)
177 max_request_size = 100;
178 return max_request_size;
179 }
180
181 static bool read_property(Display *dpy, Window win,
182 Atom property, bool deleteProperty,
183 ByteArray & buffer, int *size, Atom *type,
184 int *format, bool nullterm)
185 {
186 int maxsize = max_selection_incr(dpy);
187 unsigned long bytes_left;
188 unsigned long length;
189 unsigned char *data;
190 Atom dummy_type;
191 int dummy_format;
192
193 if (!type)
194 type = &dummy_type;
195 if (!format)
196 format = &dummy_format;
197
198 // Don't read anything but get the size of the property data
199 if (XGetWindowProperty(dpy, win, property, 0, 0, False,
200 AnyPropertyType, type, format, &length, &bytes_left, &data) != Success) {
201 buffer.clear();
202 return false;
203 }
204 XFree(data);
205
206 int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
207
208 switch (*format) {
209 case 16:
210 format_inc = sizeof(short) / 2;
211 proplen *= format_inc;
212 break;
213 case 32:
214 format_inc = sizeof(long) / 4;
215 proplen *= format_inc;
216 break;
217 }
218
219 buffer.resize(proplen + (nullterm ? 1 : 0));
220 while (bytes_left) {
221 if (XGetWindowProperty(dpy, win, property, offset, maxsize / 4,
222 False, AnyPropertyType, type, format,
223 &length, &bytes_left, &data) != Success)
224 break;
225
226 offset += length / (32 / *format);
227 length *= format_inc * (*format / 8);
228
229 memcpy(buffer.data() + buffer_offset, data, length);
230 buffer_offset += length;
231 XFree(data);
232 }
233
234 if (nullterm)
235 buffer[buffer_offset] = '\0';
236
237 if (size)
238 *size = buffer_offset;
239
240 if (deleteProperty)
241 XDeleteProperty(dpy, win, property);
242
243 XFlush(dpy);
244 return true;
245 }
246
247
248 /*
249 * Timed wait for an SelectionNotify event
250 */
251
252 static const uint64 SELECTION_MAX_WAIT = 500000; // 500 ms
253
254 static bool wait_for_selection_notify_event(Display *dpy, Window win, XEvent *event, int timeout)
255 {
256 uint64 start = GetTicks_usec();
257
258 do {
259 // Wait
260 XDisplayUnlock();
261 Delay_usec(5000);
262 XDisplayLock();
263
264 // Check for SelectionNotify event
265 if (XCheckTypedWindowEvent(dpy, win, SelectionNotify, event))
266 return true;
267
268 } while ((GetTicks_usec() - start) < timeout);
269
270 return false;
271 }
272
273
274 /*
275 * Initialization
276 */
277
278 void ClipInit(void)
279 {
280 no_clip_conversion = PrefsFindBool("noclipconversion");
281
282 // Find screen and root window
283 screen = XDefaultScreen(x_display);
284 rootwin = XRootWindow(x_display, screen);
285
286 // Create fake window to receive selection events
287 clip_win = XCreateSimpleWindow(x_display, rootwin, 0, 0, 1, 1, 0, 0, 0);
288
289 // Initialize X11 atoms
290 xa_clipboard = XInternAtom(x_display, "CLIPBOARD", False);
291 xa_targets = XInternAtom(x_display, "TARGETS", False);
292 xa_multiple = XInternAtom(x_display, "MULTIPLE", False);
293 xa_timestamp = XInternAtom(x_display, "TIMESTAMP", False);
294 xa_atom_pair = XInternAtom(x_display, "ATOM_PAIR", False);
295 }
296
297
298 /*
299 * Deinitialization
300 */
301
302 void ClipExit(void)
303 {
304 // Close window
305 XDestroyWindow(x_display, clip_win);
306 }
307
308
309 /*
310 * Mac application wrote to clipboard
311 */
312
313 void PutScrap(uint32 type, void *scrap, int32 length)
314 {
315 D(bug("PutScrap type %08lx, data %p, length %ld\n", type, scrap, length));
316 if (!REPLACE_PUTSCRAP)
317 return;
318 if (we_put_this_data) {
319 we_put_this_data = false;
320 return;
321 }
322 if (length <= 0)
323 return;
324
325 XDisplayLock();
326 do_putscrap(type, scrap, length);
327 XDisplayUnlock();
328 }
329
330 static void do_putscrap(uint32 type, void *scrap, int32 length)
331 {
332 clip_data.type = None;
333 switch (type) {
334 case FOURCC('T','E','X','T'): {
335 D(bug(" clipping TEXT\n"));
336 clip_data.type = XA_STRING;
337 clip_data.data.clear();
338 clip_data.data.reserve(length);
339
340 // Convert text from Mac charset to ISO-Latin1
341 uint8 *p = (uint8 *)scrap;
342 for (int i=0; i<length; i++) {
343 uint8 c = *p++;
344 if (c < 0x80) {
345 if (c == 13) // CR -> LF
346 c = 10;
347 } else if (!no_clip_conversion)
348 c = mac2iso[c & 0x7f];
349 clip_data.data.push_back(c);
350 }
351 break;
352 }
353
354 case FOURCC('s','t','y','l'): {
355 D(bug(" clipping styl\n"));
356 uint16 *p = (uint16 *)scrap;
357 uint16 n = ntohs(*p++);
358 D(bug(" %d styles (%d bytes)\n", n, length));
359 for (int i = 0; i < n; i++) {
360 uint32 offset = ntohl(*(uint32 *)p); p += 2;
361 uint16 line_height = ntohs(*p++);
362 uint16 font_ascent = ntohs(*p++);
363 uint16 font_family = ntohs(*p++);
364 uint16 style_code = ntohs(*p++);
365 uint16 char_size = ntohs(*p++);
366 uint16 r = ntohs(*p++);
367 uint16 g = ntohs(*p++);
368 uint16 b = ntohs(*p++);
369 D(bug(" offset=%d, height=%d, font ascent=%d, id=%d, style=%x, size=%d, RGB=%x/%x/%x\n",
370 offset, line_height, font_ascent, font_family, style_code, char_size, r, g, b));
371 }
372 break;
373 }
374 }
375
376 // Acquire selection ownership
377 if (clip_data.type != None) {
378 clip_data.time = CurrentTime;
379 while (XGetSelectionOwner(x_display, xa_clipboard) != clip_win)
380 XSetSelectionOwner(x_display, xa_clipboard, clip_win, clip_data.time);
381 }
382 }
383
384
385 /*
386 * Mac application reads clipboard
387 */
388
389 void GetScrap(void **handle, uint32 type, int32 offset)
390 {
391 D(bug("GetScrap handle %p, type %08x, offset %d\n", handle, type, offset));
392 if (!REPLACE_GETSCRAP)
393 return;
394
395 XDisplayLock();
396 do_getscrap(handle, type, offset);
397 XDisplayUnlock();
398 }
399
400 static void do_getscrap(void **handle, uint32 type, int32 offset)
401 {
402 ByteArray data;
403 XEvent event;
404
405 // If we own the selection, the data is already available on MacOS side
406 if (XGetSelectionOwner(x_display, xa_clipboard) == clip_win)
407 return;
408
409 // Check TIMESTAMP
410 #if GETSCRAP_REQUESTS_TIMESTAMP
411 static Time last_timestamp = 0;
412 XConvertSelection(x_display, xa_clipboard, xa_timestamp, xa_clipboard, clip_win, CurrentTime);
413 if (wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) &&
414 event.xselection.property != None &&
415 read_property(x_display,
416 event.xselection.requestor, event.xselection.property,
417 true, data, 0, 0, 0, false)) {
418 Time timestamp = ((long *)data.data())[0];
419 if (timestamp <= last_timestamp)
420 return;
421 }
422 last_timestamp = CurrentTime;
423 #endif
424
425 // Get TARGETS available
426 #if GETSCRAP_REQUESTS_TARGETS
427 XConvertSelection(x_display, xa_clipboard, xa_targets, xa_clipboard, clip_win, CurrentTime);
428 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
429 event.xselection.property == None ||
430 !read_property(x_display,
431 event.xselection.requestor, event.xselection.property,
432 true, data, 0, 0, 0, false))
433 return;
434 #endif
435
436 // Get appropriate format for requested data
437 Atom format = None;
438 #if GETSCRAP_REQUESTS_TARGETS
439 int n_atoms = data.size() / sizeof(long);
440 long *atoms = (long *)data.data();
441 for (int i = 0; i < n_atoms; i++) {
442 Atom target = atoms[i];
443 D(bug(" target %08x (%s)\n", target, XGetAtomName(x_display, target)));
444 switch (type) {
445 case FOURCC('T','E','X','T'):
446 D(bug(" clipping TEXT\n"));
447 if (target == XA_STRING)
448 format = target;
449 break;
450 case FOURCC('P','I','C','T'):
451 break;
452 }
453 }
454 #else
455 switch (type) {
456 case FOURCC('T','E','X','T'):
457 D(bug(" clipping TEXT\n"));
458 format = XA_STRING;
459 break;
460 case FOURCC('P','I','C','T'):
461 break;
462 }
463 #endif
464 if (format == None)
465 return;
466
467 // Get the native clipboard data
468 XConvertSelection(x_display, xa_clipboard, format, xa_clipboard, clip_win, CurrentTime);
469 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
470 event.xselection.property == None ||
471 !read_property(x_display,
472 event.xselection.requestor, event.xselection.property,
473 false, data, 0, 0, 0, format == XA_STRING))
474 return;
475
476 // Allocate space for new scrap in MacOS side
477 M68kRegisters r;
478 r.d[0] = data.size();
479 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
480 uint32 scrap_area = r.a[0];
481
482 if (scrap_area) {
483 switch (type) {
484 case FOURCC('T','E','X','T'):
485 // Convert text from ISO-Latin1 to Mac charset
486 uint8 *p = Mac2HostAddr(scrap_area);
487 for (int i = 0; i < data.size(); i++) {
488 uint8 c = data[i];
489 if (c < 0x80) {
490 if (c == 10) // LF -> CR
491 c = 13;
492 } else if (!no_clip_conversion)
493 c = iso2mac[c & 0x7f];
494 *p++ = c;
495 }
496 break;
497 }
498
499 // Add new data to clipboard
500 static uint8 proc[] = {
501 0x59, 0x8f, // subq.l #4,sp
502 0xa9, 0xfc, // ZeroScrap()
503 0x2f, 0x3c, 0, 0, 0, 0, // move.l #length,-(sp)
504 0x2f, 0x3c, 0, 0, 0, 0, // move.l #type,-(sp)
505 0x2f, 0x3c, 0, 0, 0, 0, // move.l #outbuf,-(sp)
506 0xa9, 0xfe, // PutScrap()
507 0x58, 0x8f, // addq.l #4,sp
508 M68K_RTS >> 8, M68K_RTS
509 };
510 uint32 proc_area = (uint32)proc; // FIXME: make sure 32-bit relocs are used
511 WriteMacInt32(proc_area + 6, data.size());
512 WriteMacInt32(proc_area + 12, type);
513 WriteMacInt32(proc_area + 18, scrap_area);
514 we_put_this_data = true;
515 Execute68k(proc_area, &r);
516
517 // We are done with scratch memory
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.at(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 }