gecko-dev/widget/gtk/nsClipboardX11.cpp
Martin Stransky 6d77331196 Bug 1417892 - Implement primary clipboard selection under Wayland, r=jhorak
MozReview-Commit-ID: 7TTBSbx8qPX

--HG--
extra : rebase_source : 42e117a0fdf12e811429c4470a3945e8ccd8b6fe
2018-04-04 14:49:21 +02:00

390 lines
12 KiB
C++

/* -*- 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 <gtk/gtk.h>
// For manipulation of the X event queue
#include <X11/Xlib.h>
#include <gdk/gdkx.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include "X11UndefineNone.h"
using namespace mozilla;
bool
nsRetrievalContextX11::HasSelectionSupport(void)
{
// yeah, unix supports the selection clipboard on X11.
return true;
}
static GdkFilterReturn
selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
{
XEvent *xevent = static_cast<XEvent*>(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)
, mClipboardRequestNumber(0)
, mClipboardData(nullptr)
, mClipboardDataLength(0)
, mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE))
{
// 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;
}
bool
nsRetrievalContextX11::WaitForX11Content()
{
if (mState == COMPLETED) { // the request completed synchronously
return true;
}
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) {
return true;
}
}
TimeStamp now = TimeStamp::Now();
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = std::max<int32_t>(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 false;
}
// Call this when data has been retrieved.
void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
const void* aData,
int aDataRequestNumber)
{
if (mClipboardRequestNumber != aDataRequestNumber) {
NS_WARNING("nsRetrievalContextX11::Complete() got obsoleted clipboard data.");
return;
}
if (mState == INITIAL) {
mState = COMPLETED;
MOZ_ASSERT(mClipboardData == nullptr &&
mClipboardDataLength == 0,
"We're leaking clipboard data!");
switch (aDataType) {
case CLIPBOARD_TEXT:
{
const char* text = static_cast<const char*>(aData);
if (text) {
mClipboardDataLength = sizeof(char) * (strlen(text) + 1);
mClipboardData = moz_xmalloc(mClipboardDataLength);
memcpy(mClipboardData, text, mClipboardDataLength);
}
}
break;
case CLIPBOARD_TARGETS:
{
const GtkSelectionData *selection =
static_cast<const GtkSelectionData *>(aData);
gint n_targets = 0;
GdkAtom *targets = nullptr;
if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
!n_targets) {
return;
}
mClipboardData = targets;
mClipboardDataLength = n_targets;
}
break;
case CLIPBOARD_DATA:
{
const GtkSelectionData *selection =
static_cast<const GtkSelectionData *>(aData);
gint dataLength = gtk_selection_data_get_length(selection);
if (dataLength > 0) {
mClipboardDataLength = dataLength;
mClipboardData = moz_xmalloc(dataLength);
memcpy(mClipboardData, gtk_selection_data_get_data(selection),
dataLength);
}
}
break;
}
} 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<ClipboardRequestHandler*>(data);
handler->Complete(selection_data);
delete handler;
}
static void
clipboard_text_received(GtkClipboard *clipboard,
const gchar *text,
gpointer data)
{
ClipboardRequestHandler *handler =
static_cast<ClipboardRequestHandler*>(data);
handler->Complete(text);
delete handler;
}
bool
nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
GtkClipboard *clipboard,
const char *aMimeType)
{
mState = INITIAL;
NS_ASSERTION(!mClipboardData, "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, aDataType, mClipboardRequestNumber);
switch (aDataType) {
case CLIPBOARD_DATA:
gtk_clipboard_request_contents(clipboard,
gdk_atom_intern(aMimeType, FALSE), clipboard_contents_received,
handler);
break;
case CLIPBOARD_TEXT:
gtk_clipboard_request_text(clipboard, clipboard_text_received,
handler);
break;
case CLIPBOARD_TARGETS:
gtk_clipboard_request_contents(clipboard,
mTargetMIMEType, clipboard_contents_received,
handler);
break;
}
return WaitForX11Content();
}
GdkAtom*
nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard, int* aTargetNums)
{
GtkClipboard *clipboard =
gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
if (!WaitForClipboardData(CLIPBOARD_TARGETS, clipboard))
return nullptr;
*aTargetNums = mClipboardDataLength;
GdkAtom* targets = static_cast<GdkAtom*>(mClipboardData);
// We don't hold the target list internally but we transfer the ownership.
mClipboardData = nullptr;
mClipboardDataLength = 0;
return targets;
}
const char*
nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard,
uint32_t* aContentLength)
{
GtkClipboard *clipboard;
clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
if (!WaitForClipboardData(CLIPBOARD_DATA, clipboard, aMimeType))
return nullptr;
*aContentLength = mClipboardDataLength;
return static_cast<const char*>(mClipboardData);
}
const char*
nsRetrievalContextX11::GetClipboardText(int32_t aWhichClipboard)
{
GtkClipboard *clipboard;
clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
if (!WaitForClipboardData(CLIPBOARD_TEXT, clipboard))
return nullptr;
return static_cast<const char*>(mClipboardData);
}
void nsRetrievalContextX11::ReleaseClipboardData(const char* aClipboardData)
{
NS_ASSERTION(aClipboardData == mClipboardData,
"Releasing unknown clipboard data!");
free((void*)aClipboardData);
mClipboardData = nullptr;
mClipboardDataLength = 0;
}