diff --git a/widget/src/gtk2/nsClipboard.cpp b/widget/src/gtk2/nsClipboard.cpp index cc592b9e514a..bfa831f6a645 100644 --- a/widget/src/gtk2/nsClipboard.cpp +++ b/widget/src/gtk2/nsClipboard.cpp @@ -48,6 +48,14 @@ #include #include +// For manipulation of the X event queue +#include +#include +#include +#include +#include +#include + // Callback when someone asks us for the selection void invisible_selection_get_cb (GtkWidget *aWidget, @@ -70,6 +78,34 @@ ConvertHTMLtoUCS2 (guchar *data, static void GetHTMLCharset (guchar * data, PRInt32 dataLength, nsCString& str); + +// Our own versions of gtk_clipboard_wait_for_contents and +// gtk_clipboard_wait_for_text, which don't run the event loop while +// waiting for the data. This prevents a lot of problems related to +// dispatching events at unexpected times. + +static GtkSelectionData * +wait_for_contents (GtkClipboard *clipboard, GdkAtom target); + +static gchar * +wait_for_text (GtkClipboard *clipboard); + +static Bool +checkEventProc(Display *display, XEvent *event, XPointer arg); + +static void +WaitForRetrieval(GtkClipboard *clipboard, void **transferData); + +static void +clipboard_contents_received(GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data); + +static void +clipboard_text_received(GtkClipboard *clipboard, + const gchar *text, + gpointer data); + nsClipboard::nsClipboard() { mWidget = nsnull; @@ -216,10 +252,10 @@ nsClipboard::GetData(nsITransferable *aTransferable, PRInt32 aWhichClipboard) // Special case text/unicode since we can convert any // string into text/unicode if (!strcmp(flavorStr, kUnicodeMime)) { - gchar *new_text = gtk_clipboard_wait_for_text(clipboard); + gchar* new_text = wait_for_text(clipboard); if (new_text) { // Convert utf-8 into our unicode format. - nsString ucs2string = NS_ConvertUTF8toUCS2(new_text); + NS_ConvertUTF8toUCS2 ucs2string(new_text); data = (guchar *)ToNewUnicode(ucs2string); length = ucs2string.Length() * 2; g_free(new_text); @@ -237,8 +273,7 @@ nsClipboard::GetData(nsITransferable *aTransferable, PRInt32 aWhichClipboard) // the clipboard. GdkAtom atom = gdk_atom_intern(flavorStr, FALSE); GtkSelectionData *selectionData; - selectionData = gtk_clipboard_wait_for_contents(clipboard, - atom); + selectionData = wait_for_contents(clipboard, atom); if (selectionData) { length = selectionData->length * selectionData->format / 8; // Special case text/html since we can convert into UCS2 @@ -393,9 +428,7 @@ GtkSelectionData * nsClipboard::GetTargets(GdkAtom aWhichClipboard) { GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard); - return gtk_clipboard_wait_for_contents (clipboard, - gdk_atom_intern ("TARGETS", - FALSE)); + return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE)); } nsITransferable * @@ -695,3 +728,154 @@ void GetHTMLCharset(guchar * data, PRInt32 dataLength, nsCString& str) str.Assign(NS_LITERAL_CSTRING("UNKNOWN")); } +static void +DispatchSelectionNotifyEvent(GdkDisplay *display, GtkWidget *widget, + XEvent *xevent) +{ + GdkEvent event; + event.selection.type = GDK_SELECTION_NOTIFY; + event.selection.window = widget->window; + event.selection.selection = gdk_x11_xatom_to_atom_for_display(display, xevent->xselection.selection); + event.selection.target = gdk_x11_xatom_to_atom_for_display(display, xevent->xselection.target); + event.selection.property = gdk_x11_xatom_to_atom_for_display(display, xevent->xselection.property); + event.selection.time = xevent->xselection.time; + + gtk_widget_event(widget, &event); +} + +static void +DispatchPropertyNotifyEvent(GdkDisplay *display, GtkWidget *widget, + XEvent *xevent) +{ + if (((GdkWindowObject *) widget->window)->event_mask & GDK_PROPERTY_CHANGE_MASK) { + GdkEvent event; + event.property.type = GDK_PROPERTY_NOTIFY; + event.property.window = widget->window; + event.property.atom = gdk_x11_xatom_to_atom_for_display(display, xevent->xproperty.atom); + event.property.time = xevent->xproperty.time; + event.property.state = xevent->xproperty.state; + + gtk_widget_event(widget, &event); + } +} + +struct checkEventContext +{ + Window window; + Atom selAtom; +}; + +static Bool +checkEventProc(Display *display, XEvent *event, XPointer arg) +{ + checkEventContext *context = (checkEventContext *) arg; + + return (event->xany.window == context->window && + (event->xany.type == SelectionNotify || + (event->xany.type == PropertyNotify && + event->xproperty.atom == context->selAtom))); +} + +// Idle timeout for receiving selection and property notify events (microsec) +static const int kClipboardTimeout = 500000; + +static void +WaitForRetrieval(GtkClipboard *clipboard, void **transferData) +{ + if (*transferData) // the request completed synchronously + return; + + GdkDisplay *cbDisplay = gtk_clipboard_get_display(clipboard); + Display *xDisplay = gdk_x11_display_get_xdisplay(cbDisplay); + + GtkWidget *cbWidget = NS_STATIC_CAST(GtkWidget *, + g_object_get_data(G_OBJECT(cbDisplay), + "gtk-clipboard-widget")); + + checkEventContext context; + context.window = GDK_WINDOW_XWINDOW(cbWidget->window); + context.selAtom = gdk_x11_atom_to_xatom_for_display(cbDisplay, + gdk_atom_intern("GDK_SELECTION", FALSE)); + + // Send X events which are relevant to the ongoing selection retrieval + // to the clipboard widget. Wait until either the operation completes, or + // we hit our timeout. All other X events remain queued. + + int cnumber; +#ifdef VMS + cnumber = XConnectionNumber(xDisplay); +#else + cnumber = ConnectionNumber(xDisplay); +#endif + + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(cnumber++, &rfds); + + do { + XEvent xevent; + + while (XCheckIfEvent(xDisplay, &xevent, checkEventProc, + (XPointer) &context)) { + + if (xevent.xany.type == SelectionNotify) + DispatchSelectionNotifyEvent(cbDisplay, cbWidget, &xevent); + else + DispatchPropertyNotifyEvent(cbDisplay, cbWidget, &xevent); + + if (*transferData) + return; + } + + tv.tv_sec = 0; + tv.tv_usec = kClipboardTimeout; + + } while (select(cnumber, &rfds, NULL, NULL, &tv) == 1); + +#ifdef DEBUG_CLIPBOARD + printf("exceeded clipboard timeout\n"); +#endif +} + +static void +clipboard_contents_received(GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + GtkSelectionData **sel_copy = NS_STATIC_CAST(GtkSelectionData **, data); + *sel_copy = gtk_selection_data_copy(selection_data); +} + + +static GtkSelectionData * +wait_for_contents(GtkClipboard *clipboard, GdkAtom target) +{ + GtkSelectionData *selection_data = NULL; + gtk_clipboard_request_contents(clipboard, target, + clipboard_contents_received, + &selection_data); + + WaitForRetrieval(clipboard, (void **) &selection_data); + return selection_data; +} + +static void +clipboard_text_received(GtkClipboard *clipboard, + const gchar *text, + gpointer data) +{ + gchar **text_copy = NS_STATIC_CAST(gchar **, data); + *text_copy = g_strdup(text); +} + +static gchar * +wait_for_text(GtkClipboard *clipboard) +{ + gchar *text = NULL; + gtk_clipboard_request_text(clipboard, clipboard_text_received, &text); + + WaitForRetrieval(clipboard, (void **) &text); + return text; +}