ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/Unix/clip_unix.cpp
Revision: 1.4
Committed: 2003-12-31T18:23:41Z (20 years, 6 months ago) by gbeauche
Branch: MAIN
Changes since 1.3: +0 -42 lines
Log Message:
Remove the "klipper" hack since it now (in KDE 3.2) checks for TIMESTAMP.
Besides, we are as smooth as before with last commits.

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 GetScrap() to check for TIMESTAMP and optimize out clipboard syncs?
79 #define GETSCRAP_REQUESTS_TIMESTAMP 0
80
81 // Do we want GetScrap() to first check for TARGETS available from the clipboard?
82 #define GETSCRAP_REQUESTS_TARGETS 0
83
84
85 // From main_linux.cpp
86 extern Display *x_display;
87
88
89 // Conversion tables
90 static const uint8 mac2iso[0x80] = {
91 0xc4, 0xc5, 0xc7, 0xc9, 0xd1, 0xd6, 0xdc, 0xe1,
92 0xe0, 0xe2, 0xe4, 0xe3, 0xe5, 0xe7, 0xe9, 0xe8,
93 0xea, 0xeb, 0xed, 0xec, 0xee, 0xef, 0xf1, 0xf3,
94 0xf2, 0xf4, 0xf6, 0xf5, 0xfa, 0xf9, 0xfb, 0xfc,
95 0x2b, 0xb0, 0xa2, 0xa3, 0xa7, 0xb7, 0xb6, 0xdf,
96 0xae, 0xa9, 0x20, 0xb4, 0xa8, 0x23, 0xc6, 0xd8,
97 0x20, 0xb1, 0x3c, 0x3e, 0xa5, 0xb5, 0xf0, 0x53,
98 0x50, 0x70, 0x2f, 0xaa, 0xba, 0x4f, 0xe6, 0xf8,
99 0xbf, 0xa1, 0xac, 0x2f, 0x66, 0x7e, 0x44, 0xab,
100 0xbb, 0x2e, 0x20, 0xc0, 0xc3, 0xd5, 0x4f, 0x6f,
101 0x2d, 0x2d, 0x22, 0x22, 0x60, 0x27, 0xf7, 0x20,
102 0xff, 0x59, 0x2f, 0xa4, 0x3c, 0x3e, 0x66, 0x66,
103 0x23, 0xb7, 0x2c, 0x22, 0x25, 0xc2, 0xca, 0xc1,
104 0xcb, 0xc8, 0xcd, 0xce, 0xcf, 0xcc, 0xd3, 0xd4,
105 0x20, 0xd2, 0xda, 0xdb, 0xd9, 0x69, 0x5e, 0x7e,
106 0xaf, 0x20, 0xb7, 0xb0, 0xb8, 0x22, 0xb8, 0x20
107 };
108
109 static const uint8 iso2mac[0x80] = {
110 0xad, 0xb0, 0xe2, 0xc4, 0xe3, 0xc9, 0xa0, 0xe0,
111 0xf6, 0xe4, 0xde, 0xdc, 0xce, 0xb2, 0xb3, 0xb6,
112 0xb7, 0xd4, 0xd5, 0xd2, 0xd3, 0xa5, 0xd0, 0xd1,
113 0xf7, 0xaa, 0xdf, 0xdd, 0xcf, 0xba, 0xfd, 0xd9,
114 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xbd, 0xa4,
115 0xac, 0xa9, 0xbb, 0xc7, 0xc2, 0xf0, 0xa8, 0xf8,
116 0xa1, 0xb1, 0xc3, 0xc5, 0xab, 0xb5, 0xa6, 0xe1,
117 0xfc, 0xc6, 0xbc, 0xc8, 0xf9, 0xda, 0xd7, 0xc0,
118 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82,
119 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec,
120 0xf5, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xfb,
121 0xaf, 0xf4, 0xf2, 0xf3, 0x86, 0xfa, 0xb8, 0xa7,
122 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d,
123 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95,
124 0xfe, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6,
125 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xff, 0xb9, 0xd8
126 };
127
128 // Flag: Don't convert clipboard text
129 static bool no_clip_conversion;
130
131 // Flag for PutScrap(): the data was put by GetScrap(), don't bounce it back to the Be side
132 static bool we_put_this_data = false;
133
134 // X11 variables
135 static int screen; // Screen number
136 static Window rootwin, clip_win; // Root window and the clipboard window
137 static Atom xa_clipboard;
138 static Atom xa_targets;
139 static Atom xa_multiple;
140 static Atom xa_timestamp;
141 static Atom xa_atom_pair;
142
143 // Define a byte array (rewrite if it's a bottleneck)
144 struct ByteArray : public vector<uint8> {
145 void resize(int size) { reserve(size); vector<uint8>::resize(size); }
146 uint8 *data() { return &at(0); }
147 };
148
149 // Clipboard data for requestors
150 struct ClipboardData {
151 Time time;
152 Atom type;
153 ByteArray data;
154 };
155 static ClipboardData clip_data;
156
157 // Prototypes
158 static void do_putscrap(uint32 type, void *scrap, int32 length);
159 static void do_getscrap(void **handle, uint32 type, int32 offset);
160
161
162 /*
163 * Read an X11 property (hack from QT 3.1.2)
164 */
165
166 static inline int max_selection_incr(Display *dpy)
167 {
168 int max_request_size = 4 * XMaxRequestSize(dpy);
169 if (max_request_size > 4 * 65536)
170 max_request_size = 4 * 65536;
171 else if ((max_request_size -= 100) < 0)
172 max_request_size = 100;
173 return max_request_size;
174 }
175
176 static bool read_property(Display *dpy, Window win,
177 Atom property, bool deleteProperty,
178 ByteArray & buffer, int *size, Atom *type,
179 int *format, bool nullterm)
180 {
181 int maxsize = max_selection_incr(dpy);
182 unsigned long bytes_left;
183 unsigned long length;
184 unsigned char *data;
185 Atom dummy_type;
186 int dummy_format;
187
188 if (!type)
189 type = &dummy_type;
190 if (!format)
191 format = &dummy_format;
192
193 // Don't read anything but get the size of the property data
194 if (XGetWindowProperty(dpy, win, property, 0, 0, False,
195 AnyPropertyType, type, format, &length, &bytes_left, &data) != Success) {
196 buffer.clear();
197 return false;
198 }
199 XFree(data);
200
201 int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
202
203 switch (*format) {
204 case 16:
205 format_inc = sizeof(short) / 2;
206 proplen *= format_inc;
207 break;
208 case 32:
209 format_inc = sizeof(long) / 4;
210 proplen *= format_inc;
211 break;
212 }
213
214 buffer.resize(proplen + (nullterm ? 1 : 0));
215 while (bytes_left) {
216 if (XGetWindowProperty(dpy, win, property, offset, maxsize / 4,
217 False, AnyPropertyType, type, format,
218 &length, &bytes_left, &data) != Success)
219 break;
220
221 offset += length / (32 / *format);
222 length *= format_inc * (*format / 8);
223
224 memcpy(buffer.data() + buffer_offset, data, length);
225 buffer_offset += length;
226 XFree(data);
227 }
228
229 if (nullterm)
230 buffer[buffer_offset] = '\0';
231
232 if (size)
233 *size = buffer_offset;
234
235 if (deleteProperty)
236 XDeleteProperty(dpy, win, property);
237
238 XFlush(dpy);
239 return true;
240 }
241
242
243 /*
244 * Timed wait for an SelectionNotify event
245 */
246
247 static const uint64 SELECTION_MAX_WAIT = 500000; // 500 ms
248
249 static bool wait_for_selection_notify_event(Display *dpy, Window win, XEvent *event, int timeout)
250 {
251 uint64 start = GetTicks_usec();
252
253 do {
254 // Wait
255 XDisplayUnlock();
256 Delay_usec(5000);
257 XDisplayLock();
258
259 // Check for SelectionNotify event
260 if (XCheckTypedWindowEvent(dpy, win, SelectionNotify, event))
261 return true;
262
263 } while ((GetTicks_usec() - start) < timeout);
264
265 return false;
266 }
267
268
269 /*
270 * Initialization
271 */
272
273 void ClipInit(void)
274 {
275 no_clip_conversion = PrefsFindBool("noclipconversion");
276
277 // Find screen and root window
278 screen = XDefaultScreen(x_display);
279 rootwin = XRootWindow(x_display, screen);
280
281 // Create fake window to receive selection events
282 clip_win = XCreateSimpleWindow(x_display, rootwin, 0, 0, 1, 1, 0, 0, 0);
283
284 // Initialize X11 atoms
285 xa_clipboard = XInternAtom(x_display, "CLIPBOARD", False);
286 xa_targets = XInternAtom(x_display, "TARGETS", False);
287 xa_multiple = XInternAtom(x_display, "MULTIPLE", False);
288 xa_timestamp = XInternAtom(x_display, "TIMESTAMP", False);
289 xa_atom_pair = XInternAtom(x_display, "ATOM_PAIR", False);
290 }
291
292
293 /*
294 * Deinitialization
295 */
296
297 void ClipExit(void)
298 {
299 // Close window
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 (!REPLACE_PUTSCRAP)
312 return;
313 if (we_put_this_data) {
314 we_put_this_data = false;
315 return;
316 }
317 if (length <= 0)
318 return;
319
320 XDisplayLock();
321 do_putscrap(type, scrap, length);
322 XDisplayUnlock();
323 }
324
325 static void do_putscrap(uint32 type, void *scrap, int32 length)
326 {
327 clip_data.type = None;
328 switch (type) {
329 case FOURCC('T','E','X','T'):
330 D(bug(" clipping TEXT\n"));
331 clip_data.type = XA_STRING;
332 clip_data.data.clear();
333 clip_data.data.reserve(length);
334
335 // Convert text from Mac charset to ISO-Latin1
336 uint8 *p = (uint8 *)scrap;
337 for (int i=0; i<length; i++) {
338 uint8 c = *p++;
339 if (c < 0x80) {
340 if (c == 13) // CR -> LF
341 c = 10;
342 } else if (!no_clip_conversion)
343 c = mac2iso[c & 0x7f];
344 clip_data.data.push_back(c);
345 }
346 break;
347 }
348
349 // Acquire selection ownership
350 if (clip_data.type != None) {
351 clip_data.time = CurrentTime;
352 while (XGetSelectionOwner(x_display, xa_clipboard) != clip_win)
353 XSetSelectionOwner(x_display, xa_clipboard, clip_win, clip_data.time);
354 }
355 }
356
357
358 /*
359 * Mac application reads clipboard
360 */
361
362 void GetScrap(void **handle, uint32 type, int32 offset)
363 {
364 D(bug("GetScrap handle %p, type %08x, offset %d\n", handle, type, offset));
365 if (!REPLACE_GETSCRAP)
366 return;
367
368 XDisplayLock();
369 do_getscrap(handle, type, offset);
370 XDisplayUnlock();
371 }
372
373 static void do_getscrap(void **handle, uint32 type, int32 offset)
374 {
375 ByteArray data;
376 XEvent event;
377
378 // If we own the selection, the data is already available on MacOS side
379 if (XGetSelectionOwner(x_display, xa_clipboard) == clip_win)
380 return;
381
382 // Check TIMESTAMP
383 #if GETSCRAP_REQUESTS_TIMESTAMP
384 static Time last_timestamp = 0;
385 XConvertSelection(x_display, xa_clipboard, xa_timestamp, xa_clipboard, clip_win, CurrentTime);
386 if (wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) &&
387 event.xselection.property != None &&
388 read_property(x_display,
389 event.xselection.requestor, event.xselection.property,
390 true, data, 0, 0, 0, false)) {
391 Time timestamp = ((long *)data.data())[0];
392 if (timestamp <= last_timestamp)
393 return;
394 }
395 last_timestamp = CurrentTime;
396 #endif
397
398 // Get TARGETS available
399 #if GETSCRAP_REQUESTS_TARGETS
400 XConvertSelection(x_display, xa_clipboard, xa_targets, xa_clipboard, clip_win, CurrentTime);
401 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
402 event.xselection.property == None ||
403 !read_property(x_display,
404 event.xselection.requestor, event.xselection.property,
405 true, data, 0, 0, 0, false))
406 return;
407 #endif
408
409 // Get appropriate format for requested data
410 Atom format = None;
411 #if GETSCRAP_REQUESTS_TARGETS
412 int n_atoms = data.size() / sizeof(long);
413 long *atoms = (long *)data.data();
414 for (int i = 0; i < n_atoms; i++) {
415 Atom target = atoms[i];
416 switch (type) {
417 case FOURCC('T','E','X','T'):
418 D(bug(" clipping TEXT\n"));
419 if (target == XA_STRING)
420 format = target;
421 break;
422 case FOURCC('P','I','C','T'):
423 break;
424 }
425 }
426 #else
427 switch (type) {
428 case FOURCC('T','E','X','T'):
429 D(bug(" clipping TEXT\n"));
430 format = XA_STRING;
431 break;
432 case FOURCC('P','I','C','T'):
433 break;
434 }
435 #endif
436 if (format == None)
437 return;
438
439 // Get the native clipboard data
440 XConvertSelection(x_display, xa_clipboard, format, xa_clipboard, clip_win, CurrentTime);
441 if (!wait_for_selection_notify_event(x_display, clip_win, &event, SELECTION_MAX_WAIT) ||
442 event.xselection.property == None ||
443 !read_property(x_display,
444 event.xselection.requestor, event.xselection.property,
445 false, data, 0, 0, 0, format == XA_STRING))
446 return;
447
448 // Allocate space for new scrap in MacOS side
449 M68kRegisters r;
450 r.d[0] = data.size();
451 Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
452 uint32 scrap_area = r.a[0];
453
454 if (scrap_area) {
455 switch (type) {
456 case FOURCC('T','E','X','T'):
457 // Convert text from ISO-Latin1 to Mac charset
458 uint8 *p = Mac2HostAddr(scrap_area);
459 for (int i = 0; i < data.size(); i++) {
460 uint8 c = data[i];
461 if (c < 0x80) {
462 if (c == 10) // LF -> CR
463 c = 13;
464 } else if (!no_clip_conversion)
465 c = iso2mac[c & 0x7f];
466 *p++ = c;
467 }
468 break;
469 }
470
471 // Add new data to clipboard
472 static uint8 proc[] = {
473 0x59, 0x8f, // subq.l #4,sp
474 0xa9, 0xfc, // ZeroScrap()
475 0x2f, 0x3c, 0, 0, 0, 0, // move.l #length,-(sp)
476 0x2f, 0x3c, 0, 0, 0, 0, // move.l #type,-(sp)
477 0x2f, 0x3c, 0, 0, 0, 0, // move.l #outbuf,-(sp)
478 0xa9, 0xfe, // PutScrap()
479 0x58, 0x8f, // addq.l #4,sp
480 M68K_RTS >> 8, M68K_RTS
481 };
482 uint32 proc_area = (uint32)proc; // FIXME: make sure 32-bit relocs are used
483 WriteMacInt32(proc_area + 6, data.size());
484 WriteMacInt32(proc_area + 12, type);
485 WriteMacInt32(proc_area + 18, scrap_area);
486 we_put_this_data = true;
487 Execute68k(proc_area, &r);
488
489 // We are done with scratch memory
490 r.a[0] = scrap_area;
491 Execute68kTrap(0xa01f, &r); // DisposePtr
492 }
493 }
494
495
496 /*
497 * Handle X11 selection events
498 */
499
500 void ClipboardSelectionClear(XSelectionClearEvent *xev)
501 {
502 if (xev->selection != xa_clipboard)
503 return;
504
505 D(bug("Selection cleared, lost clipboard ownership\n"));
506 clip_data.type = None;
507 clip_data.data.clear();
508 }
509
510 // Top level selection handler
511 static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple);
512
513 static bool handle_selection_TIMESTAMP(XSelectionRequestEvent *req)
514 {
515 // 32-bit integer values are always passed as a long whatever is its size
516 long timestamp = clip_data.time;
517
518 // Change requestor property
519 XChangeProperty(x_display, req->requestor, req->property,
520 XA_INTEGER, 32,
521 PropModeReplace, (uint8 *)&timestamp, 1);
522
523 return true;
524 }
525
526 static bool handle_selection_TARGETS(XSelectionRequestEvent *req)
527 {
528 // Default supported targets
529 vector<long> targets;
530 targets.push_back(xa_targets);
531 targets.push_back(xa_multiple);
532 targets.push_back(xa_timestamp);
533
534 // Extra targets matchin current clipboard data
535 if (clip_data.type != None)
536 targets.push_back(clip_data.type);
537
538 // Change requestor property
539 XChangeProperty(x_display, req->requestor, req->property,
540 xa_targets, 32,
541 PropModeReplace, (uint8 *)&targets.at(0), targets.size());
542
543 return true;
544 }
545
546 static bool handle_selection_STRING(XSelectionRequestEvent *req)
547 {
548 // Make sure we have valid data to send though ICCCM compliant
549 // clients should have first requested TARGETS to identify
550 // this possibility.
551 if (clip_data.type != XA_STRING)
552 return false;
553
554 // Send the string, it's already encoded as ISO-8859-1
555 XChangeProperty(x_display, req->requestor, req->property,
556 XA_STRING, 8,
557 PropModeReplace, (uint8 *)clip_data.data.data(), clip_data.data.size());
558
559 return true;
560 }
561
562 static bool handle_selection_MULTIPLE(XSelectionRequestEvent *req)
563 {
564 Atom rtype;
565 int rformat;
566 ByteArray data;
567
568 if (!read_property(x_display, req->requestor, req->property,
569 false, data, 0, &rtype, &rformat, 0))
570 return false;
571
572 // rtype should be ATOM_PAIR but some clients don't honour that
573 if (rformat != 32)
574 return false;
575
576 struct AtomPair { long target; long property; };
577 AtomPair *atom_pairs = (AtomPair *)data.data();
578 int n_atom_pairs = data.size() / sizeof(AtomPair);
579
580 bool handled = true;
581 if (n_atom_pairs) {
582 // Setup a new XSelectionRequestEvent when servicing individual requests
583 XSelectionRequestEvent event;
584 memcpy(&event, req, sizeof(event));
585
586 for (int i = 0; i < n_atom_pairs; i++) {
587 Atom target = atom_pairs[i].target;
588 Atom property = atom_pairs[i].property;
589
590 // None is not a valid property
591 if (property == None)
592 continue;
593
594 // Service this request
595 event.target = target;
596 event.property = property;
597 if (!handle_selection(&event, true)) {
598 /* FIXME: ICCCM 2.6.2:
599
600 If the owner fails to convert the target named by an
601 atom in the MULTIPLE property, it should replace that
602 atom in the property with None.
603 */
604 handled = false;
605 }
606 }
607 }
608
609 return handled;
610 }
611
612 static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple)
613 {
614 bool handled =false;
615
616 if (req->target == xa_timestamp)
617 handled = handle_selection_TIMESTAMP(req);
618 else if (req->target == xa_targets)
619 handled = handle_selection_TARGETS(req);
620 else if (req->target == XA_STRING)
621 handled = handle_selection_STRING(req);
622 else if (req->target == xa_multiple)
623 handled = handle_selection_MULTIPLE(req);
624
625 // Notify requestor only when we are done with his request
626 if (handled && !is_multiple) {
627 XEvent out_event;
628 out_event.xselection.type = SelectionNotify;
629 out_event.xselection.requestor = req->requestor;
630 out_event.xselection.selection = req->selection;
631 out_event.xselection.target = req->target;
632 out_event.xselection.property = handled ? req->property : None;
633 out_event.xselection.time = req->time;
634 XSendEvent(x_display, req->requestor, False, 0, &out_event);
635 }
636 }
637
638 void ClipboardSelectionRequest(XSelectionRequestEvent *req)
639 {
640 if (req->requestor == clip_win || req->selection != xa_clipboard)
641 return;
642
643 D(bug("Selection requested from 0x%lx to 0x%lx (%s) 0x%lx (%s)\n",
644 req->requestor,
645 req->selection,
646 XGetAtomName(req->display, req->selection),
647 req->target,
648 XGetAtomName(req->display, req->target)));
649
650 handle_selection(req, false);
651 }