/* -*- 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 #include #include #include #include #include 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(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(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(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(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(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(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(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( 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( 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( 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( 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( 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( 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( 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(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(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(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(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(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(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(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(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(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(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(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(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( 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(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; }