/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim:expandtab:shiftwidth=4:tabstop=4: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArrayUtils.h" #include "nsArrayUtils.h" #include "nsClipboard.h" #include "nsClipboardX11.h" #include "nsSupportsPrimitives.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsPrimitiveHelpers.h" #include "nsIServiceManager.h" #include "nsImageToPixbuf.h" #include "nsStringStream.h" #include "nsIObserverService.h" #include "mozilla/Services.h" #include "mozilla/RefPtr.h" #include "mozilla/TimeStamp.h" #include "imgIContainer.h" #include // For manipulation of the X event queue #include #include #include #include #include #include #include "X11UndefineNone.h" using namespace mozilla; static GdkFilterReturn selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data) { XEvent *xevent = static_cast(gdk_xevent); if (xevent->xany.type == SelectionRequest) { if (xevent->xselectionrequest.requestor == X11None) return GDK_FILTER_REMOVE; GdkDisplay *display = gdk_x11_lookup_xdisplay( xevent->xselectionrequest.display); if (!display) return GDK_FILTER_REMOVE; GdkWindow *window = gdk_x11_window_foreign_new_for_display(display, xevent->xselectionrequest.requestor); if (!window) return GDK_FILTER_REMOVE; g_object_unref(window); } return GDK_FILTER_CONTINUE; } nsRetrievalContextX11::nsRetrievalContextX11() : mState(INITIAL) , mData(nullptr) , mClipboardRequestNumber(0) { // A custom event filter to workaround attempting to dereference a null // selection requestor in GTK3 versions before 3.11.3. See bug 1178799. #if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) if (gtk_check_version(3, 11, 3)) gdk_window_add_filter(nullptr, selection_request_filter, nullptr); #endif } nsRetrievalContextX11::~nsRetrievalContextX11() { gdk_window_remove_filter(nullptr, selection_request_filter, nullptr); } static void DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent) { GdkEvent event; event.selection.type = GDK_SELECTION_NOTIFY; event.selection.window = gtk_widget_get_window(widget); event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection); event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target); event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property); event.selection.time = xevent->xselection.time; gtk_widget_event(widget, &event); } static void DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent) { GdkWindow *window = gtk_widget_get_window(widget); if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) { GdkEvent event; event.property.type = GDK_PROPERTY_NOTIFY; event.property.window = window; event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom); event.property.time = xevent->xproperty.time; event.property.state = xevent->xproperty.state; gtk_widget_event(widget, &event); } } struct checkEventContext { GtkWidget *cbWidget; Atom selAtom; }; static Bool checkEventProc(Display *display, XEvent *event, XPointer arg) { checkEventContext *context = (checkEventContext *) arg; if (event->xany.type == SelectionNotify || (event->xany.type == PropertyNotify && event->xproperty.atom == context->selAtom)) { GdkWindow *cbWindow = gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display), event->xany.window); if (cbWindow) { GtkWidget *cbWidget = nullptr; gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget); if (cbWidget && GTK_IS_WIDGET(cbWidget)) { context->cbWidget = cbWidget; return True; } } } return False; } void * nsRetrievalContextX11::Wait() { if (mState == COMPLETED) { // the request completed synchronously void *data = mData; mData = nullptr; return data; } GdkDisplay *gdkDisplay = gdk_display_get_default(); if (GDK_IS_X11_DISPLAY(gdkDisplay)) { Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay); checkEventContext context; context.cbWidget = nullptr; context.selAtom = gdk_x11_atom_to_xatom(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 select_result; int cnumber = ConnectionNumber(xDisplay); fd_set select_set; FD_ZERO(&select_set); FD_SET(cnumber, &select_set); ++cnumber; TimeStamp start = TimeStamp::Now(); do { XEvent xevent; while (XCheckIfEvent(xDisplay, &xevent, checkEventProc, (XPointer) &context)) { if (xevent.xany.type == SelectionNotify) DispatchSelectionNotifyEvent(context.cbWidget, &xevent); else DispatchPropertyNotifyEvent(context.cbWidget, &xevent); if (mState == COMPLETED) { void *data = mData; mData = nullptr; return data; } } TimeStamp now = TimeStamp::Now(); struct timeval tv; tv.tv_sec = 0; tv.tv_usec = std::max(0, kClipboardTimeout - (now - start).ToMicroseconds()); select_result = select(cnumber, &select_set, nullptr, nullptr, &tv); } while (select_result == 1 || (select_result == -1 && errno == EINTR)); } #ifdef DEBUG_CLIPBOARD printf("exceeded clipboard timeout\n"); #endif mState = TIMED_OUT; return nullptr; } // Call this when data has been retrieved. void nsRetrievalContextX11::Complete(GtkSelectionData* aData, int aDataRequestNumber) { if (mClipboardRequestNumber != aDataRequestNumber) { NS_WARNING("nsRetrievalContextX11::Complete() got obsoleted clipboard data."); return; } if (mState == INITIAL) { mState = COMPLETED; mData = gtk_selection_data_get_length(aData) >= 0 ? gtk_selection_data_copy(aData) : nullptr; } else { // Already timed out MOZ_ASSERT(mState == TIMED_OUT); } } static void clipboard_contents_received(GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) { ClipboardRequestHandler *handler = static_cast(data); handler->Complete(selection_data); delete handler; } GtkSelectionData* nsRetrievalContextX11::WaitForContents(GtkClipboard *clipboard, const char *aMimeType) { mState = INITIAL; NS_ASSERTION(!mData, "Leaking clipboard content!"); // Call ClipboardRequestHandler() with unique clipboard request number. // The request number pairs gtk_clipboard_request_contents() data request // with clipboard_contents_received() callback where the data // is provided by Gtk. mClipboardRequestNumber++; ClipboardRequestHandler* handler = new ClipboardRequestHandler(this, mClipboardRequestNumber); gtk_clipboard_request_contents(clipboard, gdk_atom_intern(aMimeType, FALSE), clipboard_contents_received, handler); return static_cast(Wait()); } GdkAtom* nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard, int* aTargetNums) { *aTargetNums = 0; GtkClipboard *clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)); GtkSelectionData *selection_data = WaitForContents(clipboard, "TARGETS"); if (!selection_data) return nullptr; gint n_targets = 0; GdkAtom *targets = nullptr; if (!gtk_selection_data_get_targets(selection_data, &targets, &n_targets) || !n_targets) { return nullptr; } gtk_selection_data_free(selection_data); *aTargetNums = n_targets; return targets; } const char* nsRetrievalContextX11::GetClipboardData(const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) { GtkClipboard *clipboard; clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)); GtkSelectionData *selectionData = WaitForContents(clipboard, aMimeType); if (!selectionData) return nullptr; char* clipboardData = nullptr; int contentLength = gtk_selection_data_get_length(selectionData); if (contentLength > 0) { clipboardData = reinterpret_cast( moz_xmalloc(sizeof(char)*contentLength)); memcpy(clipboardData, gtk_selection_data_get_data(selectionData), sizeof(char)*contentLength); } gtk_selection_data_free(selectionData); *aContentLength = contentLength; return (const char*)clipboardData; } void nsRetrievalContextX11::ReleaseClipboardData(const char* aClipboardData) { free((void *)aClipboardData); }