diff --git a/SheepShaver/src/Unix/clip_unix.cpp b/SheepShaver/src/Unix/clip_unix.cpp index 27abef21..41b2f0a8 100644 --- a/SheepShaver/src/Unix/clip_unix.cpp +++ b/SheepShaver/src/Unix/clip_unix.cpp @@ -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 +#include +#include +#include +#include #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 { + void resize(int size) { reserve(size); vector::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 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 *)×tamp, 1); + + return true; +} + +static bool handle_selection_TARGETS(XSelectionRequestEvent *req) +{ + // Default supported targets + vector 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; +} diff --git a/SheepShaver/src/Unix/video_x.cpp b/SheepShaver/src/Unix/video_x.cpp index 510fa751..b33c07dc 100644 --- a/SheepShaver/src/Unix/video_x.cpp +++ b/SheepShaver/src/Unix/video_x.cpp @@ -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 diff --git a/SheepShaver/src/prefs_items.cpp b/SheepShaver/src/prefs_items.cpp index d823d43b..00b7c155 100644 --- a/SheepShaver/src/prefs_items.cpp +++ b/SheepShaver/src/prefs_items.cpp @@ -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