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> |
26 |
– |
#include <set> |
55 |
|
#include <vector> |
56 |
|
|
57 |
|
#include "macos_util.h" |
65 |
|
#include "debug.h" |
66 |
|
|
67 |
|
#ifndef NO_STD_NAMESPACE |
40 |
– |
using std::set; |
68 |
|
using std::vector; |
69 |
|
#endif |
70 |
|
|
149 |
|
uint8 *data() { return &at(0); } |
150 |
|
}; |
151 |
|
|
152 |
< |
// Clipboard locks |
126 |
< |
#ifdef HAVE_PTHREADS |
127 |
< |
static pthread_mutex_t clip_lock = PTHREAD_MUTEX_INITIALIZER; |
128 |
< |
#define LOCK_CLIPBOARD pthread_mutex_lock(&clip_lock); |
129 |
< |
#define UNLOCK_CLIPBOARD pthread_mutex_unlock(&clip_lock); |
130 |
< |
#elif defined(HAVE_SPINLOCKS) |
131 |
< |
static spinlock_t clip_lock = SPIN_LOCK_UNLOCKED; |
132 |
< |
#define LOCK_CLIPBOARD spin_lock(&clip_lock) |
133 |
< |
#define UNLOCK_CLIPBOARD spin_unlock(&clip_lock) |
134 |
< |
#else |
135 |
< |
#define LOCK_CLIPBOARD |
136 |
< |
#define UNLOCK_CLIPBOARD |
137 |
< |
#endif |
138 |
< |
|
139 |
< |
// Clipboard data |
152 |
> |
// Clipboard data for requestors |
153 |
|
struct ClipboardData { |
154 |
|
Time time; |
155 |
< |
uint32 type; |
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) |
248 |
|
*/ |
249 |
|
|
250 |
|
static const uint64 SELECTION_MAX_WAIT = 500000; // 500 ms |
234 |
– |
static volatile bool xselection_event_avail; |
235 |
– |
static XSelectionEvent xselection_event; |
251 |
|
|
252 |
< |
static bool wait_for_selection_notify_event(Display *dpy, Window win, int timeout) |
252 |
> |
static bool wait_for_selection_notify_event(Display *dpy, Window win, XEvent *event, int timeout) |
253 |
|
{ |
254 |
|
uint64 start = GetTicks_usec(); |
255 |
|
|
241 |
– |
LOCK_CLIPBOARD; |
242 |
– |
xselection_event_avail = false; |
243 |
– |
UNLOCK_CLIPBOARD; |
244 |
– |
|
245 |
– |
// First wait a very short period of time < the VideoRefresh() resolution |
246 |
– |
struct timespec req = {0, 5000000}; |
247 |
– |
nanosleep(&req, NULL); |
248 |
– |
|
249 |
– |
// FIXME: Since handle_events() is in a separate thread, wait for |
250 |
– |
// a SelectionNotify event to occur and reported here. The |
251 |
– |
// resolution of this wait action should match that of VideoRefresh() |
252 |
– |
if (xselection_event_avail) |
253 |
– |
return true; |
254 |
– |
|
256 |
|
do { |
257 |
|
// Wait |
258 |
< |
struct timespec req = {0, 16666667}; |
259 |
< |
nanosleep(&req, NULL); |
258 |
> |
XDisplayUnlock(); |
259 |
> |
Delay_usec(5000); |
260 |
> |
XDisplayLock(); |
261 |
|
|
262 |
< |
// Return immediately if the event is now available |
263 |
< |
if (xselection_event_avail) |
262 |
> |
// Check for SelectionNotify event |
263 |
> |
if (XCheckTypedWindowEvent(dpy, win, SelectionNotify, event)) |
264 |
|
return true; |
265 |
|
|
266 |
|
} while ((GetTicks_usec() - start) < timeout); |
354 |
|
if (length <= 0) |
355 |
|
return; |
356 |
|
|
357 |
< |
bool did_putscrap = false; |
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 = type; |
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 |
< |
did_putscrap = true; |
375 |
< |
break; |
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 (did_putscrap) { |
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); |
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, SELECTION_MAX_WAIT) && |
424 |
< |
xselection_event.property != None) { |
425 |
< |
ByteArray data; |
426 |
< |
if (read_property(x_display, |
427 |
< |
xselection_event.requestor, xselection_event.property, |
428 |
< |
true, data, 0, 0, 0, false)) { |
429 |
< |
Time timestamp = ((long *)data.data())[0]; |
430 |
< |
if (timestamp == last_timestamp) |
409 |
< |
return; |
410 |
< |
} |
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, SELECTION_MAX_WAIT) || |
439 |
< |
xselection_event.property == None) |
440 |
< |
return; |
441 |
< |
|
421 |
< |
ByteArray data; |
422 |
< |
if (!read_property(x_display, |
423 |
< |
xselection_event.requestor, xselection_event.property, |
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 |
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, SELECTION_MAX_WAIT) || |
479 |
< |
xselection_event.property == None || |
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 |
< |
xselection_event.requestor, xselection_event.property, |
482 |
< |
false, clip_data.data, 0, 0, 0, format == XA_STRING)) |
481 |
> |
event.xselection.requestor, event.xselection.property, |
482 |
> |
false, data, 0, 0, 0, format == XA_STRING)) |
483 |
|
return; |
484 |
|
|
467 |
– |
clip_data.type = type; |
468 |
– |
clip_data.time = xselection_event.time; |
469 |
– |
|
485 |
|
// Allocate space for new scrap in MacOS side |
486 |
|
M68kRegisters r; |
487 |
< |
r.d[0] = clip_data.data.size(); |
487 |
> |
r.d[0] = data.size(); |
488 |
|
Execute68kTrap(0xa71e, &r); // NewPtrSysClear() |
489 |
|
uint32 scrap_area = r.a[0]; |
490 |
|
|
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 < clip_data.data.size(); i++) { |
497 |
< |
uint8 c = clip_data.data[i]; |
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; |
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, clip_data.data.size()); |
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; |
540 |
|
return; |
541 |
|
|
542 |
|
D(bug("Selection cleared, lost clipboard ownership\n")); |
543 |
< |
clip_data.type = 0; |
543 |
> |
clip_data.type = None; |
544 |
|
clip_data.data.clear(); |
545 |
|
} |
546 |
|
|
569 |
|
targets.push_back(xa_timestamp); |
570 |
|
|
571 |
|
// Extra targets matchin current clipboard data |
572 |
< |
switch (clip_data.type) { |
573 |
< |
case FOURCC('T','E','X','T'): |
559 |
< |
targets.push_back(XA_STRING); |
560 |
< |
break; |
561 |
< |
case FOURCC('P','I','C','T'): |
562 |
< |
break; |
563 |
< |
} |
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, |
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 != FOURCC('T','E','X','T')) |
588 |
> |
if (clip_data.type != XA_STRING) |
589 |
|
return false; |
590 |
|
|
591 |
|
// Send the string, it's already encoded as ISO-8859-1 |
691 |
|
|
692 |
|
handle_selection(req, false); |
693 |
|
} |
684 |
– |
|
685 |
– |
void ClipboardSelectionNotify(XSelectionEvent *req) |
686 |
– |
{ |
687 |
– |
if (req->requestor != clip_win || req->selection != xa_clipboard) |
688 |
– |
return; |
689 |
– |
|
690 |
– |
D(bug("Selection is now available\n")); |
691 |
– |
memcpy(&xselection_event, req, sizeof(xselection_event)); |
692 |
– |
LOCK_CLIPBOARD; |
693 |
– |
xselection_event_avail = true; |
694 |
– |
UNLOCK_CLIPBOARD; |
695 |
– |
} |