Handle copy-paste between X11 and MacOS. X11 events handling code has to

be improved in copy mode (when we own the selection to service other clients).
Also note that older klipper has a tendency to request clipboard data
several times per second.
This commit is contained in:
gbeauche 2003-12-31 11:37:26 +00:00
parent 70c1d04b2a
commit ea33a5c8b0
3 changed files with 599 additions and 13 deletions

View File

@ -1,7 +1,7 @@
/*
* clip_unix.cpp - Clipboard handling, Unix implementation
*
* SheepShaver (C) 1997-2002 Christian Bauer and Marc Hellwig
* SheepShaver (C) 1997-2003 Christian Bauer and Marc Hellwig
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,13 +21,42 @@
#include "sysdeps.h"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <pthread.h>
#include <set>
#include <vector>
#include "macos_util.h"
#include "clip.h"
#include "prefs.h"
#include "cpu_emulation.h"
#include "main.h"
#include "emul_op.h"
#define DEBUG 0
#include "debug.h"
#ifndef NO_STD_NAMESPACE
using std::set;
using std::vector;
#endif
// Do we replace PutScrap()?
#define REPLACE_PUTSCRAP 1
// Do we replace GetScrap()?
#define REPLACE_GETSCRAP 1
// Do we want PutScrap() to ignore requests from klipper?
#define PUTSCRAP_IGNORES_KLIPPER 0
// Do we want GetScrap() to check for TIMESTAMP and optimize out clipboard syncs?
#define GETSCRAP_REQUESTS_TIMESTAMP 0
// Do we want GetScrap() to first check for TARGETS available from the clipboard?
#define GETSCRAP_REQUESTS_TARGETS 0
// From main_linux.cpp
extern Display *x_display;
@ -53,6 +82,224 @@ static const uint8 mac2iso[0x80] = {
0xaf, 0x20, 0xb7, 0xb0, 0xb8, 0x22, 0xb8, 0x20
};
static const uint8 iso2mac[0x80] = {
0xad, 0xb0, 0xe2, 0xc4, 0xe3, 0xc9, 0xa0, 0xe0,
0xf6, 0xe4, 0xde, 0xdc, 0xce, 0xb2, 0xb3, 0xb6,
0xb7, 0xd4, 0xd5, 0xd2, 0xd3, 0xa5, 0xd0, 0xd1,
0xf7, 0xaa, 0xdf, 0xdd, 0xcf, 0xba, 0xfd, 0xd9,
0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xbd, 0xa4,
0xac, 0xa9, 0xbb, 0xc7, 0xc2, 0xf0, 0xa8, 0xf8,
0xa1, 0xb1, 0xc3, 0xc5, 0xab, 0xb5, 0xa6, 0xe1,
0xfc, 0xc6, 0xbc, 0xc8, 0xf9, 0xda, 0xd7, 0xc0,
0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82,
0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec,
0xf5, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xfb,
0xaf, 0xf4, 0xf2, 0xf3, 0x86, 0xfa, 0xb8, 0xa7,
0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d,
0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95,
0xfe, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6,
0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xff, 0xb9, 0xd8
};
// Flag: Don't convert clipboard text
static bool no_clip_conversion;
// Flag for PutScrap(): the data was put by GetScrap(), don't bounce it back to the Be side
static bool we_put_this_data = false;
// X11 variables
static int screen; // Screen number
static Window rootwin, clip_win; // Root window and the clipboard window
static Atom xa_clipboard;
static Atom xa_targets;
static Atom xa_multiple;
static Atom xa_timestamp;
static Atom xa_atom_pair;
// Define a byte array (rewrite if it's a bottleneck)
struct ByteArray : public vector<uint8> {
void resize(int size) { reserve(size); vector<uint8>::resize(size); }
uint8 *data() { return &at(0); }
};
// Clipboard locks
#ifdef HAVE_PTHREADS
static pthread_mutex_t clip_lock = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_CLIPBOARD pthread_mutex_lock(&clip_lock);
#define UNLOCK_CLIPBOARD pthread_mutex_unlock(&clip_lock);
#elif defined(HAVE_SPINLOCKS)
static spinlock_t clip_lock = SPIN_LOCK_UNLOCKED;
#define LOCK_CLIPBOARD spin_lock(&clip_lock)
#define UNLOCK_CLIPBOARD spin_unlock(&clip_lock)
#else
#define LOCK_CLIPBOARD
#define UNLOCK_CLIPBOARD
#endif
// Clipboard data
struct ClipboardData {
Time time;
uint32 type;
ByteArray data;
};
static ClipboardData clip_data;
/*
* Read an X11 property (hack from QT 3.1.2)
*/
static inline int max_selection_incr(Display *dpy)
{
int max_request_size = 4 * XMaxRequestSize(dpy);
if (max_request_size > 4 * 65536)
max_request_size = 4 * 65536;
else if ((max_request_size -= 100) < 0)
max_request_size = 100;
return max_request_size;
}
static bool read_property(Display *dpy, Window win,
Atom property, bool deleteProperty,
ByteArray & buffer, int *size, Atom *type,
int *format, bool nullterm)
{
int maxsize = max_selection_incr(dpy);
unsigned long bytes_left;
unsigned long length;
unsigned char *data;
Atom dummy_type;
int dummy_format;
if (!type)
type = &dummy_type;
if (!format)
format = &dummy_format;
// Don't read anything but get the size of the property data
if (XGetWindowProperty(dpy, win, property, 0, 0, False,
AnyPropertyType, type, format, &length, &bytes_left, &data) != Success) {
buffer.clear();
return false;
}
XFree(data);
int offset = 0, buffer_offset = 0, format_inc = 1, proplen = bytes_left;
switch (*format) {
case 16:
format_inc = sizeof(short) / 2;
proplen *= format_inc;
break;
case 32:
format_inc = sizeof(long) / 4;
proplen *= format_inc;
break;
}
buffer.resize(proplen + (nullterm ? 1 : 0));
while (bytes_left) {
if (XGetWindowProperty(dpy, win, property, offset, maxsize / 4,
False, AnyPropertyType, type, format,
&length, &bytes_left, &data) != Success)
break;
offset += length / (32 / *format);
length *= format_inc * (*format / 8);
memcpy(buffer.data() + buffer_offset, data, length);
buffer_offset += length;
XFree(data);
}
if (nullterm)
buffer[buffer_offset] = '\0';
if (size)
*size = buffer_offset;
if (deleteProperty)
XDeleteProperty(dpy, win, property);
XFlush(dpy);
return true;
}
/*
* Timed wait for an SelectionNotify event
*/
static const uint64 SELECTION_MAX_WAIT = 500000; // 500 ms
static volatile bool xselection_event_avail;
static XSelectionEvent xselection_event;
static bool wait_for_selection_notify_event(Display *dpy, Window win, int timeout)
{
uint64 start = GetTicks_usec();
LOCK_CLIPBOARD;
xselection_event_avail = false;
UNLOCK_CLIPBOARD;
// First wait a very short period of time < the VideoRefresh() resolution
struct timespec req = {0, 5000000};
nanosleep(&req, NULL);
// FIXME: Since handle_events() is in a separate thread, wait for
// a SelectionNotify event to occur and reported here. The
// resolution of this wait action should match that of VideoRefresh()
if (xselection_event_avail)
return true;
do {
// Wait
struct timespec req = {0, 16666667};
nanosleep(&req, NULL);
// Return immediately if the event is now available
if (xselection_event_avail)
return true;
} while ((GetTicks_usec() - start) < timeout);
return false;
}
/*
* Check for a "klipper" window which, in older versions (3.1), was
* polling and retrieving clipboard data several times per second.
*/
static bool is_klipper_window_probe(Window w);
static bool is_klipper_window_check(Window w);
static bool (*is_klipper_window)(Window w) = is_klipper_window_probe;
static Window klipper_win = None;
static bool is_klipper_window_probe(Window w)
{
// We expect "klipper" to be within the first clients to request
// data from our clipboard
static int clients_countdown = 2;
bool found = false;
char *window_name;
if (XFetchName(x_display, w, &window_name) && !strcmp(window_name, "klipper")) {
D(bug(" found and ignore clipboard requests from klipper window id: 0x%08x\n", w));
klipper_win = w;
found = true;
}
if (found || --clients_countdown <= 0)
is_klipper_window = is_klipper_window_check;
}
static bool is_klipper_window_check(Window w)
{
return w == klipper_win;
}
/*
* Initialization
@ -60,6 +307,21 @@ static const uint8 mac2iso[0x80] = {
void ClipInit(void)
{
no_clip_conversion = PrefsFindBool("noclipconversion");
// Find screen and root window
screen = XDefaultScreen(x_display);
rootwin = XRootWindow(x_display, screen);
// Create fake window to receive selection events
clip_win = XCreateSimpleWindow(x_display, rootwin, 0, 0, 1, 1, 0, 0, 0);
// Initialize X11 atoms
xa_clipboard = XInternAtom(x_display, "CLIPBOARD", False);
xa_targets = XInternAtom(x_display, "TARGETS", False);
xa_multiple = XInternAtom(x_display, "MULTIPLE", False);
xa_timestamp = XInternAtom(x_display, "TIMESTAMP", False);
xa_atom_pair = XInternAtom(x_display, "ATOM_PAIR", False);
}
@ -69,6 +331,8 @@ void ClipInit(void)
void ClipExit(void)
{
// Close window
XDestroyWindow(x_display, clip_win);
}
@ -79,32 +343,44 @@ void ClipExit(void)
void PutScrap(uint32 type, void *scrap, int32 length)
{
D(bug("PutScrap type %08lx, data %p, length %ld\n", type, scrap, length));
if (!REPLACE_PUTSCRAP)
return;
if (we_put_this_data) {
we_put_this_data = false;
return;
}
if (length <= 0)
return;
bool did_putscrap = false;
switch (type) {
case FOURCC('T','E','X','T'):
D(bug(" clipping TEXT\n"));
clip_data.type = type;
clip_data.data.clear();
clip_data.data.reserve(length);
// Convert text from Mac charset to ISO-Latin1
uint8 *buf = new uint8[length];
uint8 *p = (uint8 *)scrap;
uint8 *q = buf;
for (int i=0; i<length; i++) {
uint8 c = *p++;
if (c < 0x80) {
if (c == 13) // CR -> LF
c = 10;
} else
} else if (!no_clip_conversion)
c = mac2iso[c & 0x7f];
*q++ = c;
clip_data.data.push_back(c);
}
// Put text into cut buffer
//!! XStoreBytes(x_display, buf, length);
delete[] buf;
did_putscrap = true;
break;
}
// Acquire selection ownership
if (did_putscrap) {
clip_data.time = CurrentTime;
while (XGetSelectionOwner(x_display, xa_clipboard) != clip_win)
XSetSelectionOwner(x_display, xa_clipboard, clip_win, clip_data.time);
}
}
@ -115,10 +391,305 @@ void PutScrap(uint32 type, void *scrap, int32 length)
void GetScrap(void **handle, uint32 type, int32 offset)
{
D(bug("GetScrap handle %p, type %08x, offset %d\n", handle, type, offset));
switch (type) {
if (!REPLACE_GETSCRAP)
return;
// Check TIMESTAMP
#if GETSCRAP_REQUESTS_TIMESTAMP
static Time last_timestamp = 0;
XConvertSelection(x_display, xa_clipboard, xa_timestamp, xa_clipboard, clip_win, CurrentTime);
if (wait_for_selection_notify_event(x_display, clip_win, SELECTION_MAX_WAIT) &&
xselection_event.property != None) {
ByteArray data;
if (read_property(x_display,
xselection_event.requestor, xselection_event.property,
true, data, 0, 0, 0, false)) {
Time timestamp = ((long *)data.data())[0];
if (timestamp == last_timestamp)
return;
}
}
#endif
// Get TARGETS available
#if GETSCRAP_REQUESTS_TARGETS
XConvertSelection(x_display, xa_clipboard, xa_targets, xa_clipboard, clip_win, CurrentTime);
if (!wait_for_selection_notify_event(x_display, clip_win, SELECTION_MAX_WAIT) ||
xselection_event.property == None)
return;
ByteArray data;
if (!read_property(x_display,
xselection_event.requestor, xselection_event.property,
true, data, 0, 0, 0, false))
return;
#endif
// Get appropriate format for requested data
Atom format = None;
#if GETSCRAP_REQUESTS_TARGETS
int n_atoms = data.size() / sizeof(long);
long *atoms = (long *)data.data();
for (int i = 0; i < n_atoms; i++) {
Atom target = atoms[i];
switch (type) {
case FOURCC('T','E','X','T'):
D(bug(" clipping TEXT\n"));
//!!
if (target == XA_STRING)
format = target;
break;
case FOURCC('P','I','C','T'):
break;
}
}
#else
switch (type) {
case FOURCC('T','E','X','T'):
D(bug(" clipping TEXT\n"));
format = XA_STRING;
break;
case FOURCC('P','I','C','T'):
break;
}
#endif
if (format == None)
return;
// Get the native clipboard data
XConvertSelection(x_display, xa_clipboard, format, xa_clipboard, clip_win, CurrentTime);
if (!wait_for_selection_notify_event(x_display, clip_win, SELECTION_MAX_WAIT) ||
xselection_event.property == None ||
!read_property(x_display,
xselection_event.requestor, xselection_event.property,
false, clip_data.data, 0, 0, 0, format == XA_STRING))
return;
clip_data.type = type;
clip_data.time = xselection_event.time;
// Allocate space for new scrap in MacOS side
M68kRegisters r;
r.d[0] = clip_data.data.size();
Execute68kTrap(0xa71e, &r); // NewPtrSysClear()
uint32 scrap_area = r.a[0];
if (scrap_area) {
switch (type) {
case FOURCC('T','E','X','T'):
// Convert text from ISO-Latin1 to Mac charset
uint8 *p = Mac2HostAddr(scrap_area);
for (int i = 0; i < clip_data.data.size(); i++) {
uint8 c = clip_data.data[i];
if (c < 0x80) {
if (c == 10) // LF -> CR
c = 13;
} else if (!no_clip_conversion)
c = iso2mac[c & 0x7f];
*p++ = c;
}
break;
}
// Add new data to clipboard
static uint8 proc[] = {
0x59, 0x8f, // subq.l #4,sp
0xa9, 0xfc, // ZeroScrap()
0x2f, 0x3c, 0, 0, 0, 0, // move.l #length,-(sp)
0x2f, 0x3c, 0, 0, 0, 0, // move.l #type,-(sp)
0x2f, 0x3c, 0, 0, 0, 0, // move.l #outbuf,-(sp)
0xa9, 0xfe, // PutScrap()
0x58, 0x8f, // addq.l #4,sp
M68K_RTS >> 8, M68K_RTS
};
uint32 proc_area = (uint32)proc; // FIXME: make sure 32-bit relocs are used
WriteMacInt32(proc_area + 6, clip_data.data.size());
WriteMacInt32(proc_area + 12, type);
WriteMacInt32(proc_area + 18, scrap_area);
we_put_this_data = true;
Execute68k(proc_area, &r);
// We are done with scratch memory
r.a[0] = scrap_area;
Execute68kTrap(0xa01f, &r); // DisposePtr
}
}
/*
* Handle X11 selection events
*/
void ClipboardSelectionClear(XSelectionClearEvent *xev)
{
if (xev->selection != xa_clipboard)
return;
D(bug("Selection cleared, lost clipboard ownership\n"));
clip_data.type = 0;
clip_data.data.clear();
}
// Top level selection handler
static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple);
static bool handle_selection_TIMESTAMP(XSelectionRequestEvent *req)
{
// 32-bit integer values are always passed as a long whatever is its size
long timestamp = clip_data.time;
// Change requestor property
XChangeProperty(x_display, req->requestor, req->property,
XA_INTEGER, 32,
PropModeReplace, (uint8 *)&timestamp, 1);
return true;
}
static bool handle_selection_TARGETS(XSelectionRequestEvent *req)
{
// Default supported targets
vector<long> targets;
targets.push_back(xa_targets);
targets.push_back(xa_multiple);
targets.push_back(xa_timestamp);
// Extra targets matchin current clipboard data
switch (clip_data.type) {
case FOURCC('T','E','X','T'):
targets.push_back(XA_STRING);
break;
case FOURCC('P','I','C','T'):
break;
}
// Change requestor property
XChangeProperty(x_display, req->requestor, req->property,
xa_targets, 32,
PropModeReplace, (uint8 *)&targets.at(0), targets.size());
return true;
}
static bool handle_selection_STRING(XSelectionRequestEvent *req)
{
// Make sure we have valid data to send though ICCCM compliant
// clients should have first requested TARGETS to identify
// this possibility.
if (clip_data.type != FOURCC('T','E','X','T'))
return false;
// Send the string, it's already encoded as ISO-8859-1
XChangeProperty(x_display, req->requestor, req->property,
XA_STRING, 8,
PropModeReplace, (uint8 *)clip_data.data.data(), clip_data.data.size());
return true;
}
static bool handle_selection_MULTIPLE(XSelectionRequestEvent *req)
{
Atom rtype;
int rformat;
ByteArray data;
if (!read_property(x_display, req->requestor, req->property,
false, data, 0, &rtype, &rformat, 0))
return false;
// rtype should be ATOM_PAIR but some clients don't honour that
if (rformat != 32)
return false;
struct AtomPair { long target; long property; };
AtomPair *atom_pairs = (AtomPair *)data.data();
int n_atom_pairs = data.size() / sizeof(AtomPair);
bool handled = true;
if (n_atom_pairs) {
// Setup a new XSelectionRequestEvent when servicing individual requests
XSelectionRequestEvent event;
memcpy(&event, req, sizeof(event));
for (int i = 0; i < n_atom_pairs; i++) {
Atom target = atom_pairs[i].target;
Atom property = atom_pairs[i].property;
// None is not a valid property
if (property == None)
continue;
// Service this request
event.target = target;
event.property = property;
if (!handle_selection(&event, true)) {
/* FIXME: ICCCM 2.6.2:
If the owner fails to convert the target named by an
atom in the MULTIPLE property, it should replace that
atom in the property with None.
*/
handled = false;
}
}
}
return handled;
}
static bool handle_selection(XSelectionRequestEvent *req, bool is_multiple)
{
bool handled =false;
if (req->target == xa_timestamp)
handled = handle_selection_TIMESTAMP(req);
else if (req->target == xa_targets)
handled = handle_selection_TARGETS(req);
else if (req->target == XA_STRING)
handled = handle_selection_STRING(req);
else if (req->target == xa_multiple)
handled = handle_selection_MULTIPLE(req);
// Notify requestor only when we are done with his request
if (handled && !is_multiple) {
XEvent out_event;
out_event.xselection.type = SelectionNotify;
out_event.xselection.requestor = req->requestor;
out_event.xselection.selection = req->selection;
out_event.xselection.target = req->target;
out_event.xselection.property = handled ? req->property : None;
out_event.xselection.time = req->time;
XSendEvent(x_display, req->requestor, False, 0, &out_event);
}
}
void ClipboardSelectionRequest(XSelectionRequestEvent *req)
{
if (req->requestor == clip_win || req->selection != xa_clipboard)
return;
#if PUTSCRAP_IGNORES_KLIPPER
if (is_klipper_window(req->requestor))
return;
#endif
D(bug("Selection requested from 0x%lx to 0x%lx (%s) 0x%lx (%s)\n",
req->requestor,
req->selection,
XGetAtomName(req->display, req->selection),
req->target,
XGetAtomName(req->display, req->target)));
handle_selection(req, false);
}
void ClipboardSelectionNotify(XSelectionEvent *req)
{
if (req->requestor != clip_win || req->selection != xa_clipboard)
return;
D(bug("Selection is now available\n"));
memcpy(&xselection_event, req, sizeof(xselection_event));
LOCK_CLIPBOARD;
xselection_event_avail = true;
UNLOCK_CLIPBOARD;
}

View File

@ -1,7 +1,7 @@
/*
* video_x.cpp - Video/graphics emulation, X11 specific stuff
*
* SheepShaver (C) 1997-2002 Marc Hellwig and Christian Bauer
* SheepShaver (C) 1997-2003 Marc Hellwig and Christian Bauer
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -135,6 +135,11 @@ extern Display *x_display;
// From sys_unix.cpp
extern void SysMountFirstFloppy(void);
// From clip_unix.cpp
extern void ClipboardSelectionClear(XSelectionClearEvent *);
extern void ClipboardSelectionRequest(XSelectionRequestEvent *);
extern void ClipboardSelectionNotify(XSelectionEvent *req);
// Video acceleration through SIGSEGV
#ifdef ENABLE_VOSF
@ -1181,8 +1186,16 @@ static void handle_events(void)
for (;;) {
XEvent event;
if (!XCheckMaskEvent(x_display, eventmask, &event))
if (!XCheckMaskEvent(x_display, eventmask, &event)) {
// Handle clipboard events
if (XCheckTypedEvent(x_display, SelectionRequest, &event))
ClipboardSelectionRequest(&event.xselectionrequest);
else if (XCheckTypedEvent(x_display, SelectionClear, &event))
ClipboardSelectionClear(&event.xselectionclear);
else if (XCheckTypedEvent(x_display, SelectionNotify, &event))
ClipboardSelectionNotify(&event.xselection);
break;
}
switch (event.type) {
// Mouse button

View File

@ -51,6 +51,7 @@ prefs_desc common_prefs_items[] = {
{"nonet", TYPE_BOOLEAN, false, "don't use Ethernet"},
{"nosound", TYPE_BOOLEAN, false, "don't enable sound output"},
{"nogui", TYPE_BOOLEAN, false, "disable GUI"},
{"noclipconversion", TYPE_BOOLEAN, false, "don't convert clipboard contents"},
{"ignoresegv", TYPE_BOOLEAN, false, "ignore illegal memory accesses"},
{"jit", TYPE_BOOLEAN, false, "enable JIT compiler"},
{NULL, TYPE_END, false, NULL} // End of list
@ -72,6 +73,7 @@ void AddPrefsDefaults(void)
PrefsAddBool("nonet", false);
PrefsAddBool("nosound", false);
PrefsAddBool("nogui", false);
PrefsAddBool("noclipconversion", false);
PrefsAddBool("ignoresegv", true);
#if USE_JIT