diff options
Diffstat (limited to '')
-rw-r--r-- | widget/gtk/nsClipboardWayland.cpp | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/widget/gtk/nsClipboardWayland.cpp b/widget/gtk/nsClipboardWayland.cpp new file mode 100644 index 0000000000..f8e1031e9e --- /dev/null +++ b/widget/gtk/nsClipboardWayland.cpp @@ -0,0 +1,915 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsClipboardWayland.h" +#include "nsSupportsPrimitives.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsPrimitiveHelpers.h" +#include "nsImageToPixbuf.h" +#include "nsStringStream.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsDragService.h" +#include "mozwayland/mozwayland.h" +#include "nsWaylandDisplay.h" + +#include <gtk/gtk.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> + +using namespace mozilla; +using namespace mozilla::widget; + +const char* nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] = { + "text/plain;charset=utf-8", "UTF8_STRING", "COMPOUND_TEXT"}; + +static inline GdkDragAction wl_to_gdk_actions(uint32_t dnd_actions) { + GdkDragAction actions = GdkDragAction(0); + + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + actions = GdkDragAction(actions | GDK_ACTION_COPY); + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + actions = GdkDragAction(actions | GDK_ACTION_MOVE); + + return actions; +} + +static inline uint32_t gdk_to_wl_actions(GdkDragAction action) { + uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + + if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE)) + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + if (action & GDK_ACTION_MOVE) + dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + return dnd_actions; +} + +static GtkWidget* get_gtk_widget_for_wl_surface(struct wl_surface* surface) { + GdkWindow* gdkParentWindow = + static_cast<GdkWindow*>(wl_surface_get_user_data(surface)); + + gpointer user_data = nullptr; + gdk_window_get_user_data(gdkParentWindow, &user_data); + + return GTK_WIDGET(user_data); +} + +void DataOffer::AddMIMEType(const char* aMimeType) { + GdkAtom atom = gdk_atom_intern(aMimeType, FALSE); + mTargetMIMETypes.AppendElement(atom); +} + +GdkAtom* DataOffer::GetTargets(int* aTargetNum) { + int length = mTargetMIMETypes.Length(); + if (!length) { + *aTargetNum = 0; + return nullptr; + } + + GdkAtom* targetList = + reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * length)); + for (int32_t j = 0; j < length; j++) { + targetList[j] = mTargetMIMETypes[j]; + } + + *aTargetNum = length; + return targetList; +} + +bool DataOffer::HasTarget(const char* aMimeType) { + int length = mTargetMIMETypes.Length(); + for (int32_t j = 0; j < length; j++) { + if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE)) { + LOGCLIP(("DataOffer::HasTarget() we have mime %s\n", aMimeType)); + return true; + } + } + LOGCLIP(("DataOffer::HasTarget() missing mime %s\n", aMimeType)); + return false; +} + +char* DataOffer::GetData(wl_display* aDisplay, const char* aMimeType, + uint32_t* aContentLength) { + LOGCLIP(("DataOffer::GetData() mime %s\n", aMimeType)); + + int pipe_fd[2]; + if (pipe(pipe_fd) == -1) return nullptr; + + if (!RequestDataTransfer(aMimeType, pipe_fd[1])) { + NS_WARNING("DataOffer::RequestDataTransfer() failed!"); + close(pipe_fd[0]); + close(pipe_fd[1]); + return nullptr; + } + + close(pipe_fd[1]); + wl_display_flush(aDisplay); + + struct pollfd fds; + fds.fd = pipe_fd[0]; + fds.events = POLLIN; + int pollReturn = -1; + +#define MAX_CLIPBOARD_POLL_ATTEMPTS 10 + for (int i = 0; i < MAX_CLIPBOARD_POLL_ATTEMPTS; i++) { + pollReturn = poll(&fds, 1, kClipboardTimeout / 1000); + // ret > 0 means we have data available + // ret = 0 means poll timeout expired + // ret < 0 means poll failed with error + if (pollReturn >= 0) { + break; + } + // We should try again for EINTR/EAGAIN errors, + // quit for all other ones. + if (errno != EINTR && errno != EAGAIN) { + break; + } + } + // Quit for poll error() and timeout + if (pollReturn <= 0) { + NS_WARNING("DataOffer::RequestDataTransfer() poll timeout!"); + close(pipe_fd[0]); + return nullptr; + } + + GIOChannel* channel = g_io_channel_unix_new(pipe_fd[0]); + GError* error = nullptr; + char* clipboardData = nullptr; + + g_io_channel_set_encoding(channel, nullptr, &error); + if (!error) { + gsize length = 0; + g_io_channel_read_to_end(channel, &clipboardData, &length, &error); + if (length == 0) { + // We don't have valid clipboard data although + // g_io_channel_read_to_end() allocated clipboardData for us. + // Release it now and return nullptr to indicate + // we don't have reqested data flavour. + g_free((void*)clipboardData); + clipboardData = nullptr; + } + *aContentLength = length; + } + + if (error) { + NS_WARNING( + nsPrintfCString("Unexpected error when reading clipboard data: %s", + error->message) + .get()); + g_error_free(error); + } + + g_io_channel_unref(channel); + close(pipe_fd[0]); + + LOGCLIP((" Got clipboard data length %d\n", *aContentLength)); + return clipboardData; +} + +bool WaylandDataOffer::RequestDataTransfer(const char* aMimeType, int fd) { + if (mWaylandDataOffer) { + wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd); + return true; + } + + return false; +} + +void WaylandDataOffer::DragOfferAccept(const char* aMimeType, uint32_t aTime) { + wl_data_offer_accept(mWaylandDataOffer, aTime, aMimeType); +} + +/* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c + * here. + */ +void WaylandDataOffer::SetDragStatus(GdkDragAction aPreferredAction, + uint32_t aTime) { + uint32_t preferredAction = gdk_to_wl_actions(aPreferredAction); + uint32_t allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + + /* We only don't choose a preferred action if we don't accept any. + * If we do accept any, it is currently alway copy and move + */ + if (preferredAction != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) { + allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | + WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } + + wl_data_offer_set_actions(mWaylandDataOffer, allActions, preferredAction); + + /* Workaround Wayland D&D architecture here. To get the data_device_drop() + signal (which routes to nsDragService::GetData() call) we need to + accept at least one mime type before data_device_leave(). + + Real wl_data_offer_accept() for actualy requested data mime type is + called from nsDragService::GetData(). + */ + if (mTargetMIMETypes[0]) { + wl_data_offer_accept(mWaylandDataOffer, aTime, + gdk_atom_name(mTargetMIMETypes[0])); + } +} + +void WaylandDataOffer::SetSelectedDragAction(uint32_t aWaylandAction) { + mSelectedDragAction = aWaylandAction; +} + +GdkDragAction WaylandDataOffer::GetSelectedDragAction() { + return wl_to_gdk_actions(mSelectedDragAction); +} + +void WaylandDataOffer::SetAvailableDragActions(uint32_t aWaylandActions) { + mAvailableDragActions = aWaylandActions; +} + +GdkDragAction WaylandDataOffer::GetAvailableDragActions() { + return wl_to_gdk_actions(mAvailableDragActions); +} + +void WaylandDataOffer::SetWaylandDragContext( + nsWaylandDragContext* aDragContext) { + mDragContext = aDragContext; +} + +nsWaylandDragContext* WaylandDataOffer::GetWaylandDragContext() { + return mDragContext; +} + +static void data_offer_offer(void* data, struct wl_data_offer* wl_data_offer, + const char* type) { + auto* offer = static_cast<DataOffer*>(data); + offer->AddMIMEType(type); +} + +/* Advertise all available drag and drop actions from source. + * We don't use that but follow gdk_wayland_drag_context_commit_status() + * from gdkdnd-wayland.c here. + */ +static void data_offer_source_actions(void* data, + struct wl_data_offer* wl_data_offer, + uint32_t source_actions) { + auto* offer = static_cast<WaylandDataOffer*>(data); + offer->SetAvailableDragActions(source_actions); +} + +/* Advertise recently selected drag and drop action by compositor, based + * on source actions and user choice (key modifiers, etc.). + */ +static void data_offer_action(void* data, struct wl_data_offer* wl_data_offer, + uint32_t dnd_action) { + auto* offer = static_cast<WaylandDataOffer*>(data); + offer->SetSelectedDragAction(dnd_action); + + /* Mimic GTK which triggers the motion event callback */ + nsWaylandDragContext* dropContext = offer->GetWaylandDragContext(); + if (dropContext) { + uint32_t time; + nscoord x, y; + dropContext->GetLastDropInfo(&time, &x, &y); + + WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x, + y, time); + } +} + +/* wl_data_offer callback description: + * + * data_offer_offer - Is called for each MIME type available at wl_data_offer. + * data_offer_source_actions - This event indicates the actions offered by + * the data source. + * data_offer_action - This event indicates the action selected by + * the compositor after matching the source/destination + * side actions. + */ +static const moz_wl_data_offer_listener data_offer_listener = { + data_offer_offer, data_offer_source_actions, data_offer_action}; + +WaylandDataOffer::WaylandDataOffer(wl_data_offer* aWaylandDataOffer) + : mWaylandDataOffer(aWaylandDataOffer), + mDragContext(nullptr), + mSelectedDragAction(0), + mAvailableDragActions(0) { + wl_data_offer_add_listener( + mWaylandDataOffer, (struct wl_data_offer_listener*)&data_offer_listener, + this); +} + +WaylandDataOffer::~WaylandDataOffer(void) { + if (mWaylandDataOffer) { + wl_data_offer_destroy(mWaylandDataOffer); + } +} + +bool PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd) { + if (mPrimaryDataOfferGtk) { + gtk_primary_selection_offer_receive(mPrimaryDataOfferGtk, aMimeType, fd); + return true; + } + if (mPrimaryDataOfferZwpV1) { + zwp_primary_selection_offer_v1_receive(mPrimaryDataOfferZwpV1, aMimeType, + fd); + return true; + } + return false; +} + +static void primary_data_offer( + void* data, gtk_primary_selection_offer* primary_selection_offer, + const char* mime_type) { + auto* offer = static_cast<DataOffer*>(data); + offer->AddMIMEType(mime_type); +} + +static void primary_data_offer( + void* data, zwp_primary_selection_offer_v1* primary_selection_offer, + const char* mime_type) { + auto* offer = static_cast<DataOffer*>(data); + offer->AddMIMEType(mime_type); +} + +/* gtk_primary_selection_offer_listener callback description: + * + * primary_data_offer - Is called for each MIME type available at + * gtk_primary_selection_offer. + */ +static const struct gtk_primary_selection_offer_listener + primary_selection_offer_listener_gtk = {primary_data_offer}; + +static const struct zwp_primary_selection_offer_v1_listener + primary_selection_offer_listener_zwp_v1 = {primary_data_offer}; + +PrimaryDataOffer::PrimaryDataOffer( + gtk_primary_selection_offer* aPrimaryDataOffer) + : mPrimaryDataOfferGtk(aPrimaryDataOffer), mPrimaryDataOfferZwpV1(nullptr) { + gtk_primary_selection_offer_add_listener( + aPrimaryDataOffer, &primary_selection_offer_listener_gtk, this); +} + +PrimaryDataOffer::PrimaryDataOffer( + zwp_primary_selection_offer_v1* aPrimaryDataOffer) + : mPrimaryDataOfferGtk(nullptr), mPrimaryDataOfferZwpV1(aPrimaryDataOffer) { + zwp_primary_selection_offer_v1_add_listener( + aPrimaryDataOffer, &primary_selection_offer_listener_zwp_v1, this); +} + +PrimaryDataOffer::~PrimaryDataOffer(void) { + if (mPrimaryDataOfferGtk) { + gtk_primary_selection_offer_destroy(mPrimaryDataOfferGtk); + } + if (mPrimaryDataOfferZwpV1) { + zwp_primary_selection_offer_v1_destroy(mPrimaryDataOfferZwpV1); + } +} + +NS_IMPL_ISUPPORTS(nsWaylandDragContext, nsISupports); + +nsWaylandDragContext::nsWaylandDragContext(WaylandDataOffer* aDataOffer, + wl_display* aDisplay) + : mDataOffer(aDataOffer), + mDisplay(aDisplay), + mTime(0), + mGtkWidget(nullptr), + mX(0), + mY(0) { + aDataOffer->SetWaylandDragContext(this); +} + +void nsWaylandDragContext::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime, + nscoord aX, nscoord aY) { + mTime = aTime; + mGtkWidget = aGtkWidget; + mX = aX; + mY = aY; +} + +void nsWaylandDragContext::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) { + mTime = aTime; + mX = aX; + mY = aY; +} + +void nsWaylandDragContext::GetLastDropInfo(uint32_t* aTime, nscoord* aX, + nscoord* aY) { + *aTime = mTime; + *aX = mX; + *aY = mY; +} + +void nsWaylandDragContext::SetDragStatus(GdkDragAction aPreferredAction) { + mDataOffer->SetDragStatus(aPreferredAction, mTime); +} + +GdkDragAction nsWaylandDragContext::GetAvailableDragActions() { + GdkDragAction gdkAction = mDataOffer->GetSelectedDragAction(); + + // We emulate gdk_drag_context_get_actions() here. + if (!gdkAction) { + gdkAction = mDataOffer->GetAvailableDragActions(); + } + + return gdkAction; +} + +GList* nsWaylandDragContext::GetTargets() { + int targetNums; + GdkAtom* atoms = mDataOffer->GetTargets(&targetNums); + + GList* targetList = nullptr; + for (int i = 0; i < targetNums; i++) { + targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i])); + } + + return targetList; +} + +char* nsWaylandDragContext::GetData(const char* aMimeType, + uint32_t* aContentLength) { + mDataOffer->DragOfferAccept(aMimeType, mTime); + return mDataOffer->GetData(mDisplay, aMimeType, aContentLength); +} + +void nsRetrievalContextWayland::RegisterNewDataOffer( + wl_data_offer* aWaylandDataOffer) { + DataOffer* dataOffer = static_cast<DataOffer*>( + g_hash_table_lookup(mActiveOffers, aWaylandDataOffer)); + MOZ_ASSERT( + dataOffer == nullptr, + "Registered WaylandDataOffer already exists. Wayland protocol error?"); + + if (!dataOffer) { + dataOffer = new WaylandDataOffer(aWaylandDataOffer); + g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer); + } +} + +void nsRetrievalContextWayland::RegisterNewDataOffer( + gtk_primary_selection_offer* aPrimaryDataOffer) { + DataOffer* dataOffer = static_cast<DataOffer*>( + g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer)); + MOZ_ASSERT( + dataOffer == nullptr, + "Registered PrimaryDataOffer already exists. Wayland protocol error?"); + + if (!dataOffer) { + dataOffer = new PrimaryDataOffer(aPrimaryDataOffer); + g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer); + } +} + +void nsRetrievalContextWayland::RegisterNewDataOffer( + zwp_primary_selection_offer_v1* aPrimaryDataOffer) { + DataOffer* dataOffer = static_cast<DataOffer*>( + g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer)); + MOZ_ASSERT( + dataOffer == nullptr, + "Registered PrimaryDataOffer already exists. Wayland protocol error?"); + + if (!dataOffer) { + dataOffer = new PrimaryDataOffer(aPrimaryDataOffer); + g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer); + } +} + +void nsRetrievalContextWayland::SetClipboardDataOffer( + wl_data_offer* aWaylandDataOffer) { + // Delete existing clipboard data offer + mClipboardOffer = nullptr; + + // null aWaylandDataOffer indicates that our clipboard content + // is no longer valid and should be release. + if (aWaylandDataOffer != nullptr) { + DataOffer* dataOffer = static_cast<DataOffer*>( + g_hash_table_lookup(mActiveOffers, aWaylandDataOffer)); + NS_ASSERTION(dataOffer, "We're missing stored clipboard data offer!"); + if (dataOffer) { + g_hash_table_remove(mActiveOffers, aWaylandDataOffer); + mClipboardOffer = WrapUnique(dataOffer); + } + } +} + +void nsRetrievalContextWayland::SetPrimaryDataOffer( + gtk_primary_selection_offer* aPrimaryDataOffer) { + // Release any primary offer we have. + mPrimaryOffer = nullptr; + + // aPrimaryDataOffer can be null which means we lost + // the mouse selection. + if (aPrimaryDataOffer) { + DataOffer* dataOffer = static_cast<DataOffer*>( + g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer)); + NS_ASSERTION(dataOffer, "We're missing primary data offer!"); + if (dataOffer) { + g_hash_table_remove(mActiveOffers, aPrimaryDataOffer); + mPrimaryOffer = WrapUnique(dataOffer); + } + } +} + +void nsRetrievalContextWayland::SetPrimaryDataOffer( + zwp_primary_selection_offer_v1* aPrimaryDataOffer) { + // Release any primary offer we have. + mPrimaryOffer = nullptr; + + // aPrimaryDataOffer can be null which means we lost + // the mouse selection. + if (aPrimaryDataOffer) { + DataOffer* dataOffer = static_cast<DataOffer*>( + g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer)); + NS_ASSERTION(dataOffer, "We're missing primary data offer!"); + if (dataOffer) { + g_hash_table_remove(mActiveOffers, aPrimaryDataOffer); + mPrimaryOffer = WrapUnique(dataOffer); + } + } +} + +void nsRetrievalContextWayland::AddDragAndDropDataOffer( + wl_data_offer* aDropDataOffer) { + // Remove any existing D&D contexts. + mDragContext = nullptr; + + WaylandDataOffer* dataOffer = static_cast<WaylandDataOffer*>( + g_hash_table_lookup(mActiveOffers, aDropDataOffer)); + NS_ASSERTION(dataOffer, "We're missing drag and drop data offer!"); + if (dataOffer) { + g_hash_table_remove(mActiveOffers, aDropDataOffer); + mDragContext = new nsWaylandDragContext(dataOffer, mDisplay->GetDisplay()); + } +} + +nsWaylandDragContext* nsRetrievalContextWayland::GetDragContext(void) { + return mDragContext; +} + +void nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) { + mDragContext = nullptr; +} + +// We have a new fresh data content. +// We should attach listeners to it and save for further use. +static void data_device_data_offer(void* data, + struct wl_data_device* data_device, + struct wl_data_offer* offer) { + LOGCLIP(("data_device_data_offer() callback\n")); + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->RegisterNewDataOffer(offer); +} + +// The new fresh data content is clipboard. +static void data_device_selection(void* data, + struct wl_data_device* wl_data_device, + struct wl_data_offer* offer) { + LOGCLIP(("data_device_selection() callback\n")); + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->SetClipboardDataOffer(offer); +} + +// The new fresh wayland data content is drag and drop. +static void data_device_enter(void* data, struct wl_data_device* data_device, + uint32_t time, struct wl_surface* surface, + int32_t x_fixed, int32_t y_fixed, + struct wl_data_offer* offer) { + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->AddDragAndDropDataOffer(offer); + + nsWaylandDragContext* dragContext = context->GetDragContext(); + + GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface); + if (!gtkWidget) { + NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!"); + return; + } + + LOGDRAG(("nsWindow data_device_enter for GtkWidget %p\n", (void*)gtkWidget)); + dragContext->DropDataEnter(gtkWidget, time, wl_fixed_to_int(x_fixed), + wl_fixed_to_int(y_fixed)); +} + +static void data_device_leave(void* data, struct wl_data_device* data_device) { + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + + nsWaylandDragContext* dropContext = context->GetDragContext(); + WindowDragLeaveHandler(dropContext->GetWidget()); + + LOGDRAG(("nsWindow data_device_leave for GtkWidget %p\n", + (void*)dropContext->GetWidget())); + context->ClearDragAndDropDataOffer(); +} + +static void data_device_motion(void* data, struct wl_data_device* data_device, + uint32_t time, int32_t x_fixed, + int32_t y_fixed) { + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + + nsWaylandDragContext* dropContext = context->GetDragContext(); + + nscoord x = wl_fixed_to_int(x_fixed); + nscoord y = wl_fixed_to_int(y_fixed); + dropContext->DropMotion(time, x, y); + + LOGDRAG(("nsWindow data_device_motion for GtkWidget %p\n", + (void*)dropContext->GetWidget())); + WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x, y, + time); +} + +static void data_device_drop(void* data, struct wl_data_device* data_device) { + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + nsWaylandDragContext* dropContext = context->GetDragContext(); + + uint32_t time; + nscoord x, y; + dropContext->GetLastDropInfo(&time, &x, &y); + + LOGDRAG(("nsWindow data_device_drop GtkWidget %p\n", + (void*)dropContext->GetWidget())); + WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, x, y, + time); +} + +/* wl_data_device callback description: + * + * data_device_data_offer - It's called when there's a new wl_data_offer + * available. We need to attach wl_data_offer_listener + * to it to get available MIME types. + * + * data_device_selection - It's called when the new wl_data_offer + * is a clipboard content. + * + * data_device_enter - It's called when the new wl_data_offer is a drag & drop + * content and it's tied to actual wl_surface. + * data_device_leave - It's called when the wl_data_offer (drag & dop) is not + * valid any more. + * data_device_motion - It's called when the drag and drop selection moves + * across wl_surface. + * data_device_drop - It's called when D&D operation is sucessfully finished + * and we can read the data from D&D. + * It's generated only if we call wl_data_offer_accept() and + * wl_data_offer_set_actions() from data_device_motion + * callback. + */ +static const struct wl_data_device_listener data_device_listener = { + data_device_data_offer, data_device_enter, data_device_leave, + data_device_motion, data_device_drop, data_device_selection}; + +static void primary_selection_data_offer( + void* data, struct gtk_primary_selection_device* primary_selection_device, + struct gtk_primary_selection_offer* primary_offer) { + LOGCLIP(("primary_selection_data_offer() callback\n")); + // create and add listener + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->RegisterNewDataOffer(primary_offer); +} + +static void primary_selection_data_offer( + void* data, + struct zwp_primary_selection_device_v1* primary_selection_device, + struct zwp_primary_selection_offer_v1* primary_offer) { + LOGCLIP(("primary_selection_data_offer() callback\n")); + // create and add listener + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->RegisterNewDataOffer(primary_offer); +} + +static void primary_selection_selection( + void* data, struct gtk_primary_selection_device* primary_selection_device, + struct gtk_primary_selection_offer* primary_offer) { + LOGCLIP(("primary_selection_selection() callback\n")); + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->SetPrimaryDataOffer(primary_offer); +} + +static void primary_selection_selection( + void* data, + struct zwp_primary_selection_device_v1* primary_selection_device, + struct zwp_primary_selection_offer_v1* primary_offer) { + LOGCLIP(("primary_selection_selection() callback\n")); + nsRetrievalContextWayland* context = + static_cast<nsRetrievalContextWayland*>(data); + context->SetPrimaryDataOffer(primary_offer); +} + +/* gtk_primary_selection_device callback description: + * + * primary_selection_data_offer - It's called when there's a new + * gtk_primary_selection_offer available. We need to + * attach gtk_primary_selection_offer_listener to it + * to get available MIME types. + * + * primary_selection_selection - It's called when the new + * gtk_primary_selection_offer is a primary selection + * content. It can be also called with + * gtk_primary_selection_offer = null which means + * there's no primary selection. + */ +static const struct gtk_primary_selection_device_listener + primary_selection_device_listener_gtk = { + primary_selection_data_offer, + primary_selection_selection, +}; + +static const struct zwp_primary_selection_device_v1_listener + primary_selection_device_listener_zwp_v1 = { + primary_selection_data_offer, + primary_selection_selection, +}; + +bool nsRetrievalContextWayland::HasSelectionSupport(void) { + return (mDisplay->GetPrimarySelectionDeviceManagerZwpV1() != nullptr || + mDisplay->GetPrimarySelectionDeviceManagerGtk() != nullptr); +} + +nsRetrievalContextWayland::nsRetrievalContextWayland(void) + : mInitialized(false), + mDisplay(WaylandDisplayGet()), + mActiveOffers(g_hash_table_new(NULL, NULL)), + mClipboardOffer(nullptr), + mPrimaryOffer(nullptr), + mDragContext(nullptr), + mClipboardRequestNumber(0), + mClipboardData(nullptr), + mClipboardDataLength(0) { + wl_data_device* dataDevice = wl_data_device_manager_get_data_device( + mDisplay->GetDataDeviceManager(), mDisplay->GetSeat()); + wl_data_device_add_listener(dataDevice, &data_device_listener, this); + + if (mDisplay->GetPrimarySelectionDeviceManagerZwpV1()) { + zwp_primary_selection_device_v1* primaryDataDevice = + zwp_primary_selection_device_manager_v1_get_device( + mDisplay->GetPrimarySelectionDeviceManagerZwpV1(), + mDisplay->GetSeat()); + zwp_primary_selection_device_v1_add_listener( + primaryDataDevice, &primary_selection_device_listener_zwp_v1, this); + } else if (mDisplay->GetPrimarySelectionDeviceManagerGtk()) { + gtk_primary_selection_device* primaryDataDevice = + gtk_primary_selection_device_manager_get_device( + mDisplay->GetPrimarySelectionDeviceManagerGtk(), + mDisplay->GetSeat()); + gtk_primary_selection_device_add_listener( + primaryDataDevice, &primary_selection_device_listener_gtk, this); + } + + mInitialized = true; +} + +static gboolean offer_hash_remove(gpointer wl_offer, gpointer aDataOffer, + gpointer user_data) { +#ifdef DEBUG + nsPrintfCString msg("nsRetrievalContextWayland(): leaked nsDataOffer %p\n", + aDataOffer); + NS_WARNING(msg.get()); +#endif + delete static_cast<DataOffer*>(aDataOffer); + return true; +} + +nsRetrievalContextWayland::~nsRetrievalContextWayland(void) { + g_hash_table_foreach_remove(mActiveOffers, offer_hash_remove, nullptr); + g_hash_table_destroy(mActiveOffers); +} + +GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard, + int* aTargetNum) { + if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) { + if (mClipboardOffer) { + return mClipboardOffer->GetTargets(aTargetNum); + } + } else { + if (mPrimaryOffer) { + return mPrimaryOffer->GetTargets(aTargetNum); + } + } + + *aTargetNum = 0; + return nullptr; +} + +struct FastTrackClipboard { + FastTrackClipboard(int aClipboardRequestNumber, + nsRetrievalContextWayland* aRetrievalContex) + : mClipboardRequestNumber(aClipboardRequestNumber), + mRetrievalContex(aRetrievalContex) {} + + int mClipboardRequestNumber; + nsRetrievalContextWayland* mRetrievalContex; +}; + +static void wayland_clipboard_contents_received( + GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) { + LOGCLIP(("wayland_clipboard_contents_received() callback\n")); + FastTrackClipboard* fastTrack = static_cast<FastTrackClipboard*>(data); + fastTrack->mRetrievalContex->TransferFastTrackClipboard( + fastTrack->mClipboardRequestNumber, selection_data); + delete fastTrack; +} + +void nsRetrievalContextWayland::TransferFastTrackClipboard( + int aClipboardRequestNumber, GtkSelectionData* aSelectionData) { + if (mClipboardRequestNumber == aClipboardRequestNumber) { + int dataLength = gtk_selection_data_get_length(aSelectionData); + if (dataLength > 0) { + mClipboardDataLength = dataLength; + mClipboardData = reinterpret_cast<char*>( + g_malloc(sizeof(char) * (mClipboardDataLength + 1))); + memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData), + sizeof(char) * mClipboardDataLength); + mClipboardData[mClipboardDataLength] = '\0'; + } + } else { + NS_WARNING("Received obsoleted clipboard data!"); + } +} + +const char* nsRetrievalContextWayland::GetClipboardData( + const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) { + NS_ASSERTION(mClipboardData == nullptr && mClipboardDataLength == 0, + "Looks like we're leaking clipboard data here!"); + + LOGCLIP(("nsRetrievalContextWayland::GetClipboardData [%p] mime %s\n", this, + aMimeType)); + + /* If actual clipboard data is owned by us we don't need to go + * through Wayland but we ask Gtk+ to directly call data + * getter callback nsClipboard::SelectionGetEvent(). + * see gtk_selection_convert() at gtk+/gtkselection.c. + */ + GdkAtom selection = GetSelectionAtom(aWhichClipboard); + if (gdk_selection_owner_get(selection)) { + LOGCLIP((" Internal clipboard content\n")); + mClipboardRequestNumber++; + gtk_clipboard_request_contents( + gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE), + wayland_clipboard_contents_received, + new FastTrackClipboard(mClipboardRequestNumber, this)); + } else { + LOGCLIP((" Remote clipboard content\n")); + const auto& dataOffer = + (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer; + if (!dataOffer) { + // Something went wrong. We're requested to provide clipboard data + // but we haven't got any from wayland. + NS_WARNING("Requested data without valid DataOffer!"); + mClipboardData = nullptr; + mClipboardDataLength = 0; + } else { + mClipboardData = dataOffer->GetData(mDisplay->GetDisplay(), aMimeType, + &mClipboardDataLength); + } + } + + *aContentLength = mClipboardDataLength; + return reinterpret_cast<const char*>(mClipboardData); +} + +const char* nsRetrievalContextWayland::GetClipboardText( + int32_t aWhichClipboard) { + LOGCLIP(("nsRetrievalContextWayland::GetClipboardText [%p]\n", this)); + + GdkAtom selection = GetSelectionAtom(aWhichClipboard); + const auto& dataOffer = + (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer; + if (!dataOffer) return nullptr; + + for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) { + if (dataOffer->HasTarget(sTextMimeTypes[i])) { + uint32_t unused; + return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, &unused); + } + } + return nullptr; +} + +void nsRetrievalContextWayland::ReleaseClipboardData( + const char* aClipboardData) { + LOGCLIP(("nsRetrievalContextWayland::ReleaseClipboardData [%p]\n", this)); + + NS_ASSERTION(aClipboardData == mClipboardData, + "Releasing unknown clipboard data!"); + g_free((void*)aClipboardData); + + mClipboardData = nullptr; + mClipboardDataLength = 0; +} |