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

# 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
50 #include "sysdeps.h"
51
52 #include <X11/Xlib.h>
53 #include <X11/Xatom.h>
54 #include <pthread.h>
55 #include <vector>
56
57 #include "macos_util.h"
58 #include "clip.h"
59 #include "prefs.h"
60 #include "cpu_emulation.h"
61 #include "main.h"
62 #include "emul_op.h"
63
64 #define DEBUG 0
65 #include "debug.h"
66
67 #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
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 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 // Clipboard data for requestors
153 struct ClipboardData {
154 Time time;
155 Atom type;
156 ByteArray data;
157 };
158 static ClipboardData clip_data;
159
160 // 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
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 static bool wait_for_selection_notify_event(Display *dpy, Window win, XEvent *event, int timeout)
253 {
254 uint64 start = GetTicks_usec();
255
256 do {
257 // Wait
258 XDisplayUnlock();
259 Delay_usec(5000);
260 XDisplayLock();
261
262 // Check for SelectionNotify event
263 if (XCheckTypedWindowEvent(dpy, win, SelectionNotify, event))
264 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
306 /*
307 * Initialization
308 */
309
310 void ClipInit(void)
311 {
312 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 }
328
329
330 /*
331 * Deinitialization
332 */
333
334 void ClipExit(void)
335 {
336 // Close window
337 XDestroyWindow(x_display, clip_win);
338 }
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 if (!REPLACE_PUTSCRAP)
349 return;
350 if (we_put_this_data) {
351 we_put_this_data = false;
352 return;
353 }
354 if (length <= 0)
355 return;
356
357 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 switch (type) {
366 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 }
385
386 // Acquire selection ownership
387 if (clip_data.type != None) {
388 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 }
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 if (!REPLACE_GETSCRAP)
403 return;
404
405 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 // 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 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 }
432 last_timestamp = CurrentTime;
433 #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 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 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 switch (type) {
465 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 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
479 event.xselection.property == None ||
480 !read_property(x_display,
481 event.xselection.requestor, event.xselection.property,
482 false, data, 0, 0, 0, format == XA_STRING))
483 return;
484
485 // Allocate space for new scrap in MacOS side
486 M68kRegisters r;
487 r.d[0] = data.size();
488 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
489 uint32 scrap_area = r.a[0];
490
491 if (scrap_area) {
492 switch (type) {
493 case FOURCC('T','E','X','T'):
494 // Convert text from ISO-Latin1 to Mac charset
495 uint8 *p = Mac2HostAddr(scrap_area);
496 for (int i = 0; i < data.size(); i++) {
497 uint8 c = data[i];
498 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 break;
506 }
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 WriteMacInt32(proc_area + 6, data.size());
521 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 }
530 }
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 clip_data.type = None;
544 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 if (clip_data.type != None)
573 targets.push_back(clip_data.type);
574
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 if (clip_data.type != XA_STRING)
589 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 }