Bug 1734943 [wpt PR 31170] - Correct scrolling contents cull rect, a=testonly
[gecko.git] / widget / gtk / nsClipboardWayland.cpp
blob48455c110120ba5ab5d78f0a5860ea229823e4ad
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/ArrayUtils.h"
10 #include "nsArrayUtils.h"
11 #include "nsClipboard.h"
12 #include "nsClipboardWayland.h"
13 #include "nsSupportsPrimitives.h"
14 #include "nsString.h"
15 #include "nsReadableUtils.h"
16 #include "nsPrimitiveHelpers.h"
17 #include "nsImageToPixbuf.h"
18 #include "nsStringStream.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/TimeStamp.h"
21 #include "nsDragService.h"
22 #include "mozwayland/mozwayland.h"
23 #include "nsWaylandDisplay.h"
24 #include "nsWindow.h"
25 #include "mozilla/ScopeExit.h"
26 #include "mozilla/StaticPrefs_widget.h"
27 #include "nsThreadUtils.h"
29 #include <gtk/gtk.h>
30 #include <poll.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <fcntl.h>
34 #include <errno.h>
36 using namespace mozilla;
37 using namespace mozilla::widget;
39 const char* nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] = {
40 "text/plain;charset=utf-8", "UTF8_STRING", "COMPOUND_TEXT"};
42 static inline GdkDragAction wl_to_gdk_actions(uint32_t dnd_actions) {
43 GdkDragAction actions = GdkDragAction(0);
45 if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
46 actions = GdkDragAction(actions | GDK_ACTION_COPY);
47 if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
48 actions = GdkDragAction(actions | GDK_ACTION_MOVE);
50 return actions;
53 static inline uint32_t gdk_to_wl_actions(GdkDragAction action) {
54 uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
56 if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE))
57 dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
58 if (action & GDK_ACTION_MOVE)
59 dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
61 return dnd_actions;
64 static GtkWidget* get_gtk_widget_for_wl_surface(struct wl_surface* surface) {
65 GdkWindow* gdkParentWindow =
66 static_cast<GdkWindow*>(wl_surface_get_user_data(surface));
68 gpointer user_data = nullptr;
69 gdk_window_get_user_data(gdkParentWindow, &user_data);
71 return GTK_WIDGET(user_data);
74 static void data_offer_offer(void* data, struct wl_data_offer* wl_data_offer,
75 const char* type) {
76 auto* offer = static_cast<DataOffer*>(data);
77 LOGCLIP("Data offer %p add MIME %s\n", wl_data_offer, type);
78 offer->AddMIMEType(type);
81 /* Advertise all available drag and drop actions from source.
82 * We don't use that but follow gdk_wayland_drag_context_commit_status()
83 * from gdkdnd-wayland.c here.
85 static void data_offer_source_actions(void* data,
86 struct wl_data_offer* wl_data_offer,
87 uint32_t source_actions) {
88 auto* dragContext = static_cast<DataOffer*>(data);
89 dragContext->SetAvailableDragActions(source_actions);
92 /* Advertise recently selected drag and drop action by compositor, based
93 * on source actions and user choice (key modifiers, etc.).
95 static void data_offer_action(void* data, struct wl_data_offer* wl_data_offer,
96 uint32_t dnd_action) {
97 auto* dropContext = static_cast<DataOffer*>(data);
98 dropContext->SetSelectedDragAction(dnd_action);
100 if (dropContext->GetWidget()) {
101 uint32_t time;
102 nscoord x, y;
103 dropContext->GetLastDropInfo(&time, &x, &y);
104 WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x,
105 y, time);
109 /* wl_data_offer callback description:
111 * data_offer_offer - Is called for each MIME type available at wl_data_offer.
112 * data_offer_source_actions - This event indicates the actions offered by
113 * the data source.
114 * data_offer_action - This event indicates the action selected by
115 * the compositor after matching the source/destination
116 * side actions.
118 static const moz_wl_data_offer_listener data_offer_listener = {
119 data_offer_offer, data_offer_source_actions, data_offer_action};
121 DataOffer::DataOffer(wl_data_offer* aDataOffer)
122 : mWaylandDataOffer(aDataOffer),
123 mMutex("DataOffer"),
124 mAsyncContentLength(),
125 mAsyncContentData(),
126 mGetterFinished(),
127 mSelectedDragAction(),
128 mAvailableDragActions(),
129 mTime(),
130 mGtkWidget(),
131 mX(),
132 mY() {
133 if (mWaylandDataOffer) {
134 wl_data_offer_add_listener(
135 mWaylandDataOffer, (struct wl_data_offer_listener*)&data_offer_listener,
136 this);
140 DataOffer::~DataOffer() {
141 g_clear_pointer(&mWaylandDataOffer, wl_data_offer_destroy);
143 // Async transfer was finished after time limit. In such case release the
144 // clipboard data.
145 if (mGetterFinished && mAsyncContentLength && mAsyncContentData) {
146 g_free((void*)mAsyncContentData);
150 bool DataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
151 LOGCLIP("DataOffer::RequestDataTransfer MIME %s FD %d\n", aMimeType, fd);
152 if (mWaylandDataOffer) {
153 wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd);
154 return true;
157 return false;
160 void DataOffer::AddMIMEType(const char* aMimeType) {
161 GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
162 mTargetMIMETypes.AppendElement(atom);
165 GdkAtom* DataOffer::GetTargets(int* aTargetNum) {
166 int length = mTargetMIMETypes.Length();
167 if (!length) {
168 *aTargetNum = 0;
169 return nullptr;
172 GdkAtom* targetList =
173 reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * length));
174 for (int32_t j = 0; j < length; j++) {
175 targetList[j] = mTargetMIMETypes[j];
178 *aTargetNum = length;
179 return targetList;
182 bool DataOffer::HasTarget(const char* aMimeType) {
183 int length = mTargetMIMETypes.Length();
184 for (int32_t j = 0; j < length; j++) {
185 if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE)) {
186 LOGCLIP("DataOffer::HasTarget() we have mime %s\n", aMimeType);
187 return true;
190 LOGCLIP("DataOffer::HasTarget() missing mime %s\n", aMimeType);
191 return false;
194 static bool MakeFdNonBlocking(int fd) {
195 return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) != -1;
198 char* DataOffer::GetDataInternal(const char* aMimeType,
199 uint32_t* aContentLength) {
200 LOGCLIP("GetDataInternal() mime %s\n", aMimeType);
202 int pipe_fd[2];
203 if (pipe(pipe_fd) == -1) {
204 return nullptr;
207 GIOChannel* channel = nullptr;
209 auto free = mozilla::MakeScopeExit([&] {
210 if (channel) {
211 g_io_channel_unref(channel);
213 if (pipe_fd[0] >= 0) {
214 close(pipe_fd[0]);
216 if (pipe_fd[1] >= 0) {
217 close(pipe_fd[1]);
221 if (!MakeFdNonBlocking(pipe_fd[0]) || !MakeFdNonBlocking(pipe_fd[1])) {
222 return nullptr;
225 if (!RequestDataTransfer(aMimeType, pipe_fd[1])) {
226 NS_WARNING("DataOffer::RequestDataTransfer() failed!");
227 return nullptr;
230 close(pipe_fd[1]);
231 pipe_fd[1] = -1;
233 // Flush wl_display connection to get clipboard data uploaded from display to
234 // our pipe.
235 wl_display_flush(WaylandDisplayGet()->GetDisplay());
237 channel = g_io_channel_unix_new(pipe_fd[0]);
238 GError* error = nullptr;
239 char* clipboardData = nullptr;
241 GIOStatus ret;
242 ret = g_io_channel_set_encoding(channel, nullptr, &error);
243 g_clear_pointer(&error, g_error_free);
244 if (ret != G_IO_STATUS_NORMAL) {
245 NS_WARNING("g_io_channel_set_encoding failed!");
246 return nullptr;
249 const PRTime entryTime = PR_Now();
250 gsize len;
251 while (1) {
252 LOGCLIP("reading data...\n");
253 ret = g_io_channel_read_to_end(channel, &clipboardData, &len, &error);
254 if (ret == G_IO_STATUS_NORMAL) {
255 break;
257 if (ret == G_IO_STATUS_AGAIN) {
258 wl_display_flush(WaylandDisplayGet()->GetDisplay());
259 // check the number of iterations
260 PR_Sleep(20 * PR_TicksPerSecond() / 1000); /* sleep for 20 ms/iteration */
261 if (PR_Now() - entryTime > kClipboardTimeout) {
262 break;
264 } else { // G_IO_STATUS_ERROR
265 if (error) {
266 NS_WARNING(
267 nsPrintfCString("Unexpected error when reading clipboard data: %s",
268 error->message)
269 .get());
270 g_error_free(error);
272 return nullptr;
275 *aContentLength = len;
277 if (*aContentLength == 0) {
278 // We don't have valid clipboard data although
279 // g_io_channel_read_to_end() allocated clipboardData for us.
280 // Release it now and return nullptr to indicate
281 // we don't have reqested data flavour.
282 g_free((void*)clipboardData);
283 clipboardData = nullptr;
286 LOGCLIP(" Got clipboard data length %d\n", *aContentLength);
287 return clipboardData;
290 void DataOffer::GetDataAsyncInternal(const char* aMimeType) {
291 mAsyncContentData = GetDataInternal(aMimeType, &mAsyncContentLength);
292 mGetterFinished = true;
295 char* DataOffer::GetData(const char* aMimeType, uint32_t* aContentLength) {
296 LOGCLIP("DataOffer::GetData() mime %s\n", aMimeType);
298 if (!HasTarget(aMimeType)) {
299 LOGCLIP(" Failed: DataOffer does not contain %s MIME!\n", aMimeType);
300 return nullptr;
303 return GetDataInternal(aMimeType, aContentLength);
306 char* DataOffer::GetDataAsync(const char* aMimeType, uint32_t* aContentLength) {
307 LOGCLIP("DataOffer::GetDataAsync() mime %s\n", aMimeType);
309 if (!HasTarget(aMimeType)) {
310 LOGCLIP(" Failed: DataOffer does not contain %s MIME!\n", aMimeType);
311 return nullptr;
314 if (!mMutex.TryLock()) {
315 LOGCLIP(" Failed: DataOffer is already used!\n");
316 return nullptr;
318 auto unlock = mozilla::MakeScopeExit([&] {
319 // If async getter was sucessful transfer ownership of the clipboard data
320 // to caller.
321 if (mGetterFinished) {
322 mAsyncContentLength = 0;
323 mAsyncContentData = nullptr;
324 } else {
325 // Remove offers for failed transfers
326 LOGCLIP(" data offer was not finished in time, clearing\n");
327 g_clear_pointer(&mWaylandDataOffer, wl_data_offer_destroy);
329 mMutex.Unlock();
332 mAsyncContentLength = 0;
333 mAsyncContentData = nullptr;
334 mGetterFinished = false;
336 RefPtr<DataOffer> offer(this);
337 NS_DispatchBackgroundTask(
338 NS_NewRunnableFunction("DataOffer::GetDataInternal",
339 [offer, aMimeType]() -> void {
340 offer->GetDataAsyncInternal(aMimeType);
342 nsIEventTarget::NS_DISPATCH_NORMAL);
344 int iteration = 1;
345 PRTime entryTime = PR_Now();
346 while (!mGetterFinished) {
347 if (iteration++ > kClipboardFastIterationNum) {
348 PR_Sleep(PR_MillisecondsToInterval(10)); /* sleep for 10 ms/iteration */
349 if (PR_Now() - entryTime > kClipboardTimeout) {
350 LOGCLIP(" hit time limit\n");
351 break;
354 LOGCLIP("doing iteration %d msec %ld ...\n", (iteration - 1),
355 (long)((PR_Now() - entryTime) / 1000));
356 gtk_main_iteration();
359 if (!mGetterFinished) {
360 LOGCLIP(" failed to get async clipboard data in time limit\n");
361 *aContentLength = 0;
362 return nullptr;
365 LOGCLIP(" ineration over, got data %p len %d\n", mAsyncContentData,
366 mAsyncContentLength);
367 *aContentLength = mAsyncContentLength;
368 return mAsyncContentData;
371 void DataOffer::DragOfferAccept(const char* aMimeType) {
372 LOGDRAG("DataOffer::DragOfferAccept MIME %s mTime %d\n", aMimeType, mTime);
373 if (!HasTarget(aMimeType)) {
374 LOGCLIP(" DataOffer: DataOffer does not contain %s MIME!\n", aMimeType);
375 return;
377 wl_data_offer_accept(mWaylandDataOffer, mTime, aMimeType);
380 /* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c
381 * here.
383 void DataOffer::SetDragStatus(GdkDragAction aPreferredAction) {
384 uint32_t preferredAction = gdk_to_wl_actions(aPreferredAction);
385 uint32_t allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
387 LOGDRAG("DataOffer::SetDragStatus aPreferredAction %d\n", aPreferredAction);
389 /* We only don't choose a preferred action if we don't accept any.
390 * If we do accept any, it is currently alway copy and move
392 if (preferredAction != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) {
393 allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
394 WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
397 wl_data_offer_set_actions(mWaylandDataOffer, allActions, preferredAction);
399 /* Workaround Wayland D&D architecture here. To get the data_device_drop()
400 signal (which routes to nsDragService::GetData() call) we need to
401 accept at least one mime type before data_device_leave().
403 Real wl_data_offer_accept() for actualy requested data mime type is
404 called from nsDragService::GetData().
406 if (mTargetMIMETypes[0]) {
407 wl_data_offer_accept(mWaylandDataOffer, mTime,
408 gdk_atom_name(mTargetMIMETypes[0]));
412 void DataOffer::SetSelectedDragAction(uint32_t aWaylandAction) {
413 mSelectedDragAction = aWaylandAction;
416 GdkDragAction DataOffer::GetSelectedDragAction() {
417 return wl_to_gdk_actions(mSelectedDragAction);
420 void DataOffer::SetAvailableDragActions(uint32_t aWaylandActions) {
421 mAvailableDragActions = aWaylandActions;
424 bool PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
425 if (mPrimaryDataOfferGtk) {
426 gtk_primary_selection_offer_receive(mPrimaryDataOfferGtk, aMimeType, fd);
427 return true;
429 if (mPrimaryDataOfferZwpV1) {
430 zwp_primary_selection_offer_v1_receive(mPrimaryDataOfferZwpV1, aMimeType,
431 fd);
432 return true;
434 return false;
437 static void primary_data_offer(
438 void* data, gtk_primary_selection_offer* primary_selection_offer,
439 const char* mime_type) {
440 LOGCLIP("Primary data offer %p add MIME %s\n", primary_selection_offer,
441 mime_type);
442 auto* offer = static_cast<DataOffer*>(data);
443 offer->AddMIMEType(mime_type);
446 static void primary_data_offer(
447 void* data, zwp_primary_selection_offer_v1* primary_selection_offer,
448 const char* mime_type) {
449 LOGCLIP("Primary data offer %p add MIME %s\n", primary_selection_offer,
450 mime_type);
451 auto* offer = static_cast<DataOffer*>(data);
452 offer->AddMIMEType(mime_type);
455 /* gtk_primary_selection_offer_listener callback description:
457 * primary_data_offer - Is called for each MIME type available at
458 * gtk_primary_selection_offer.
460 static const struct gtk_primary_selection_offer_listener
461 primary_selection_offer_listener_gtk = {primary_data_offer};
463 static const struct zwp_primary_selection_offer_v1_listener
464 primary_selection_offer_listener_zwp_v1 = {primary_data_offer};
466 PrimaryDataOffer::PrimaryDataOffer(
467 gtk_primary_selection_offer* aPrimaryDataOffer)
468 : DataOffer(nullptr),
469 mPrimaryDataOfferGtk(aPrimaryDataOffer),
470 mPrimaryDataOfferZwpV1(nullptr) {
471 gtk_primary_selection_offer_add_listener(
472 aPrimaryDataOffer, &primary_selection_offer_listener_gtk, this);
475 PrimaryDataOffer::PrimaryDataOffer(
476 zwp_primary_selection_offer_v1* aPrimaryDataOffer)
477 : DataOffer(nullptr),
478 mPrimaryDataOfferGtk(nullptr),
479 mPrimaryDataOfferZwpV1(aPrimaryDataOffer) {
480 zwp_primary_selection_offer_v1_add_listener(
481 aPrimaryDataOffer, &primary_selection_offer_listener_zwp_v1, this);
484 PrimaryDataOffer::~PrimaryDataOffer(void) {
485 if (mPrimaryDataOfferGtk) {
486 gtk_primary_selection_offer_destroy(mPrimaryDataOfferGtk);
488 if (mPrimaryDataOfferZwpV1) {
489 zwp_primary_selection_offer_v1_destroy(mPrimaryDataOfferZwpV1);
493 void DataOffer::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime, nscoord aX,
494 nscoord aY) {
495 mTime = aTime;
496 mGtkWidget = aGtkWidget;
497 mX = aX;
498 mY = aY;
501 void DataOffer::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) {
502 mTime = aTime;
503 mX = aX;
504 mY = aY;
507 void DataOffer::GetLastDropInfo(uint32_t* aTime, nscoord* aX, nscoord* aY) {
508 *aTime = mTime;
509 *aX = mX;
510 *aY = mY;
513 GdkDragAction DataOffer::GetAvailableDragActions() {
514 GdkDragAction gdkAction = GetSelectedDragAction();
516 // We emulate gdk_drag_context_get_actions() here.
517 if (!gdkAction) {
518 gdkAction = wl_to_gdk_actions(mAvailableDragActions);
521 return gdkAction;
524 GList* DataOffer::GetDragTargets() {
525 int targetNums;
526 GdkAtom* atoms = GetTargets(&targetNums);
528 GList* targetList = nullptr;
529 for (int i = 0; i < targetNums; i++) {
530 targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i]));
533 return targetList;
536 char* DataOffer::GetDragData(const char* aMimeType, uint32_t* aContentLength) {
537 LOGDRAG("DataOffer::GetData %s\n", aMimeType);
538 if (!HasTarget(aMimeType)) {
539 return nullptr;
541 DragOfferAccept(aMimeType);
542 return GetDataAsync(aMimeType, aContentLength);
545 RefPtr<DataOffer> nsRetrievalContextWayland::FindActiveOffer(
546 wl_data_offer* aDataOffer, bool aRemove) {
547 const int len = mActiveOffers.Length();
548 for (int i = 0; i < len; i++) {
549 if (mActiveOffers[i] && mActiveOffers[i]->MatchesOffer(aDataOffer)) {
550 RefPtr<DataOffer> ret = mActiveOffers[i];
551 if (aRemove) {
552 mActiveOffers[i] = nullptr;
554 return ret;
557 return nullptr;
560 void nsRetrievalContextWayland::InsertOffer(RefPtr<DataOffer> aDataOffer) {
561 const int len = mActiveOffers.Length();
562 for (int i = 0; i < len; i++) {
563 if (!mActiveOffers[i]) {
564 mActiveOffers[i] = aDataOffer;
565 return;
568 mActiveOffers.AppendElement(aDataOffer);
571 void nsRetrievalContextWayland::RegisterNewDataOffer(
572 wl_data_offer* aDataOffer) {
573 LOGCLIP(
574 "nsRetrievalContextWayland::RegisterNewDataOffer (wl_data_offer) %p\n",
575 aDataOffer);
577 if (FindActiveOffer(aDataOffer)) {
578 LOGCLIP(" offer already exists, protocol error?\n");
579 return;
582 InsertOffer(new DataOffer(aDataOffer));
585 void nsRetrievalContextWayland::RegisterNewDataOffer(
586 gtk_primary_selection_offer* aPrimaryDataOffer) {
587 LOGCLIP("nsRetrievalContextWayland::RegisterNewDataOffer (primary) %p\n",
588 aPrimaryDataOffer);
590 if (FindActiveOffer((wl_data_offer*)aPrimaryDataOffer)) {
591 LOGCLIP(" offer already exists, protocol error?\n");
592 return;
595 InsertOffer(new PrimaryDataOffer(aPrimaryDataOffer));
598 void nsRetrievalContextWayland::RegisterNewDataOffer(
599 zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
600 LOGCLIP("nsRetrievalContextWayland::RegisterNewDataOffer (primary ZWP) %p\n",
601 aPrimaryDataOffer);
603 if (FindActiveOffer((wl_data_offer*)aPrimaryDataOffer)) {
604 LOGCLIP(" offer already exists, protocol error?\n");
605 return;
608 InsertOffer(new PrimaryDataOffer(aPrimaryDataOffer));
611 void nsRetrievalContextWayland::SetClipboardDataOffer(
612 wl_data_offer* aDataOffer) {
613 LOGCLIP(
614 "nsRetrievalContextWayland::SetClipboardDataOffer (wl_data_offer) %p\n",
615 aDataOffer);
617 // Delete existing clipboard data offer
618 mClipboardOffer = nullptr;
620 // null aDataOffer indicates that our clipboard content
621 // is no longer valid and should be release.
622 if (aDataOffer) {
623 mClipboardOffer = FindActiveOffer(aDataOffer, /* remove */ true);
627 void nsRetrievalContextWayland::SetPrimaryDataOffer(
628 gtk_primary_selection_offer* aPrimaryDataOffer) {
629 LOGCLIP("nsRetrievalContextWayland::SetPrimaryDataOffer (primary) %p\n",
630 aPrimaryDataOffer);
632 // Release any primary offer we have.
633 mPrimaryOffer = nullptr;
635 // aPrimaryDataOffer can be null which means we lost
636 // the mouse selection.
637 if (aPrimaryDataOffer) {
638 mPrimaryOffer =
639 FindActiveOffer((wl_data_offer*)aPrimaryDataOffer, /* remove */ true);
643 void nsRetrievalContextWayland::SetPrimaryDataOffer(
644 zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
645 LOGCLIP("nsRetrievalContextWayland::SetPrimaryDataOffer (primary ZWP)%p\n",
646 aPrimaryDataOffer);
648 // Release any primary offer we have.
649 mPrimaryOffer = nullptr;
651 // aPrimaryDataOffer can be null which means we lost
652 // the mouse selection.
653 if (aPrimaryDataOffer) {
654 mPrimaryOffer =
655 FindActiveOffer((wl_data_offer*)aPrimaryDataOffer, /* remove */ true);
659 void nsRetrievalContextWayland::AddDragAndDropDataOffer(
660 wl_data_offer* aDropDataOffer) {
661 LOGDRAG("nsRetrievalContextWayland::AddDragAndDropDataOffer %p\n",
662 aDropDataOffer);
664 // Remove any existing D&D contexts.
665 mDragContext = nullptr;
666 if (aDropDataOffer) {
667 mDragContext = FindActiveOffer(aDropDataOffer, /* remove */ true);
671 // We have a new fresh data content.
672 // We should attach listeners to it and save for further use.
673 static void data_device_data_offer(void* data,
674 struct wl_data_device* data_device,
675 struct wl_data_offer* offer) {
676 LOGCLIP("data_device_data_offer(), wl_data_offer %p\n", offer);
677 nsRetrievalContextWayland* context =
678 static_cast<nsRetrievalContextWayland*>(data);
679 context->RegisterNewDataOffer(offer);
682 // The new fresh data content is clipboard.
683 static void data_device_selection(void* data,
684 struct wl_data_device* wl_data_device,
685 struct wl_data_offer* offer) {
686 LOGCLIP("data_device_selection(), set wl_data_offer %p\n", offer);
687 nsRetrievalContextWayland* context =
688 static_cast<nsRetrievalContextWayland*>(data);
689 context->SetClipboardDataOffer(offer);
692 // The new fresh wayland data content is drag and drop.
693 static void data_device_enter(void* data, struct wl_data_device* data_device,
694 uint32_t time, struct wl_surface* surface,
695 int32_t x_fixed, int32_t y_fixed,
696 struct wl_data_offer* offer) {
697 LOGDRAG("nsWindow data_device_enter");
698 nsRetrievalContextWayland* context =
699 static_cast<nsRetrievalContextWayland*>(data);
700 context->AddDragAndDropDataOffer(offer);
702 RefPtr<DataOffer> dragContext = context->GetDragContext();
703 if (dragContext) {
704 GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface);
705 if (!gtkWidget) {
706 NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!");
707 return;
710 LOGDRAG("nsWindow data_device_enter for GtkWidget %p\n", (void*)gtkWidget);
711 dragContext->DropDataEnter(gtkWidget, time, wl_fixed_to_int(x_fixed),
712 wl_fixed_to_int(y_fixed));
716 static void data_device_leave(void* data, struct wl_data_device* data_device) {
717 LOGDRAG("nsWindow data_device_leave");
718 nsRetrievalContextWayland* context =
719 static_cast<nsRetrievalContextWayland*>(data);
721 RefPtr<DataOffer> dropContext = context->GetDragContext();
722 if (dropContext) {
723 WindowDragLeaveHandler(dropContext->GetWidget());
725 LOGDRAG("nsWindow data_device_leave for GtkWidget %p\n",
726 (void*)dropContext->GetWidget());
727 context->ClearDragAndDropDataOffer();
731 static void data_device_motion(void* data, struct wl_data_device* data_device,
732 uint32_t time, int32_t x_fixed,
733 int32_t y_fixed) {
734 LOGDRAG("nsWindow data_device_motion");
735 nsRetrievalContextWayland* context =
736 static_cast<nsRetrievalContextWayland*>(data);
738 RefPtr<DataOffer> dropContext = context->GetDragContext();
739 if (dropContext) {
740 nscoord x = wl_fixed_to_int(x_fixed);
741 nscoord y = wl_fixed_to_int(y_fixed);
742 dropContext->DropMotion(time, x, y);
744 LOGDRAG("nsWindow data_device_motion for GtkWidget %p\n",
745 (void*)dropContext->GetWidget());
746 WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x,
747 y, time);
751 static void data_device_drop(void* data, struct wl_data_device* data_device) {
752 LOGDRAG("nsWindow data_device_drop");
753 nsRetrievalContextWayland* context =
754 static_cast<nsRetrievalContextWayland*>(data);
756 RefPtr<DataOffer> dropContext = context->GetDragContext();
757 if (dropContext) {
758 uint32_t time;
759 nscoord x, y;
760 dropContext->GetLastDropInfo(&time, &x, &y);
762 LOGDRAG("nsWindow data_device_drop GtkWidget %p\n",
763 (void*)dropContext->GetWidget());
764 WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
765 time);
769 /* wl_data_device callback description:
771 * data_device_data_offer - It's called when there's a new wl_data_offer
772 * available. We need to attach wl_data_offer_listener
773 * to it to get available MIME types.
775 * data_device_selection - It's called when the new wl_data_offer
776 * is a clipboard content.
777 * data_device_enter - It's called when the new wl_data_offer is a drag & drop
778 * content and it's tied to actual wl_surface.
780 * data_device_leave - It's called when the wl_data_offer (drag & dop) is not
781 * valid any more.
782 * data_device_motion - It's called when the drag and drop selection moves
783 * across wl_surface.
784 * data_device_drop - It's called when D&D operation is sucessfully finished
785 * and we can read the data from D&D.
786 * It's generated only if we call wl_data_offer_accept() and
787 * wl_data_offer_set_actions() from data_device_motion
788 * callback.
790 static const struct wl_data_device_listener data_device_listener = {
791 data_device_data_offer, data_device_enter, data_device_leave,
792 data_device_motion, data_device_drop, data_device_selection};
794 static void primary_selection_data_offer(
795 void* data, struct gtk_primary_selection_device* primary_selection_device,
796 struct gtk_primary_selection_offer* primary_offer) {
797 LOGCLIP("primary_selection_data_offer()\n");
798 // create and add listener
799 nsRetrievalContextWayland* context =
800 static_cast<nsRetrievalContextWayland*>(data);
801 context->RegisterNewDataOffer(primary_offer);
804 static void primary_selection_data_offer(
805 void* data,
806 struct zwp_primary_selection_device_v1* primary_selection_device,
807 struct zwp_primary_selection_offer_v1* primary_offer) {
808 LOGCLIP("primary_selection_data_offer()\n");
809 // create and add listener
810 nsRetrievalContextWayland* context =
811 static_cast<nsRetrievalContextWayland*>(data);
812 context->RegisterNewDataOffer(primary_offer);
815 static void primary_selection_selection(
816 void* data, struct gtk_primary_selection_device* primary_selection_device,
817 struct gtk_primary_selection_offer* primary_offer) {
818 LOGCLIP("primary_selection_selection()\n");
819 nsRetrievalContextWayland* context =
820 static_cast<nsRetrievalContextWayland*>(data);
821 context->SetPrimaryDataOffer(primary_offer);
824 static void primary_selection_selection(
825 void* data,
826 struct zwp_primary_selection_device_v1* primary_selection_device,
827 struct zwp_primary_selection_offer_v1* primary_offer) {
828 LOGCLIP("primary_selection_selection()\n");
829 nsRetrievalContextWayland* context =
830 static_cast<nsRetrievalContextWayland*>(data);
831 context->SetPrimaryDataOffer(primary_offer);
834 /* gtk_primary_selection_device callback description:
836 * primary_selection_data_offer - It's called when there's a new
837 * gtk_primary_selection_offer available. We need to
838 * attach gtk_primary_selection_offer_listener to it
839 * to get available MIME types.
841 * primary_selection_selection - It's called when the new
842 * gtk_primary_selection_offer is a primary selection
843 * content. It can be also called with
844 * gtk_primary_selection_offer = null which means
845 * there's no primary selection.
847 static const struct gtk_primary_selection_device_listener
848 primary_selection_device_listener_gtk = {
849 primary_selection_data_offer,
850 primary_selection_selection,
853 static const struct zwp_primary_selection_device_v1_listener
854 primary_selection_device_listener_zwp_v1 = {
855 primary_selection_data_offer,
856 primary_selection_selection,
859 bool nsRetrievalContextWayland::HasSelectionSupport(void) {
860 return (mDisplay->GetPrimarySelectionDeviceManagerZwpV1() != nullptr ||
861 mDisplay->GetPrimarySelectionDeviceManagerGtk() != nullptr);
864 void nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) {
865 LOGDRAG("nsRetrievalContextWayland::ClearDragAndDropDataOffer()\n");
866 mDragContext = nullptr;
869 nsRetrievalContextWayland::nsRetrievalContextWayland(void)
870 : mDisplay(WaylandDisplayGet()),
871 mClipboardRequestNumber(0),
872 mClipboardData(nullptr),
873 mClipboardDataLength(0),
874 mAsyncDataGetter(
875 StaticPrefs::widget_wayland_async_data_transfer_enabled_AtStartup()) {
876 wl_data_device* dataDevice = wl_data_device_manager_get_data_device(
877 mDisplay->GetDataDeviceManager(), mDisplay->GetSeat());
878 wl_data_device_add_listener(dataDevice, &data_device_listener, this);
880 if (mDisplay->GetPrimarySelectionDeviceManagerZwpV1()) {
881 zwp_primary_selection_device_v1* primaryDataDevice =
882 zwp_primary_selection_device_manager_v1_get_device(
883 mDisplay->GetPrimarySelectionDeviceManagerZwpV1(),
884 mDisplay->GetSeat());
885 zwp_primary_selection_device_v1_add_listener(
886 primaryDataDevice, &primary_selection_device_listener_zwp_v1, this);
887 } else if (mDisplay->GetPrimarySelectionDeviceManagerGtk()) {
888 gtk_primary_selection_device* primaryDataDevice =
889 gtk_primary_selection_device_manager_get_device(
890 mDisplay->GetPrimarySelectionDeviceManagerGtk(),
891 mDisplay->GetSeat());
892 gtk_primary_selection_device_add_listener(
893 primaryDataDevice, &primary_selection_device_listener_gtk, this);
897 nsRetrievalContextWayland::~nsRetrievalContextWayland(void) {}
899 struct FastTrackClipboard {
900 FastTrackClipboard(ClipboardDataType aDataType, int aClipboardRequestNumber,
901 RefPtr<nsRetrievalContextWayland> aRetrievalContex)
902 : mClipboardRequestNumber(aClipboardRequestNumber),
903 mRetrievalContex(std::move(aRetrievalContex)),
904 mDataType(aDataType) {}
905 int mClipboardRequestNumber;
906 RefPtr<nsRetrievalContextWayland> mRetrievalContex;
907 ClipboardDataType mDataType;
910 static void wayland_clipboard_contents_received(
911 GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) {
912 LOGCLIP("wayland_clipboard_contents_received() selection_data = %p\n",
913 selection_data);
914 FastTrackClipboard* fastTrack = static_cast<FastTrackClipboard*>(data);
915 fastTrack->mRetrievalContex->TransferFastTrackClipboard(
916 fastTrack->mDataType, fastTrack->mClipboardRequestNumber, selection_data);
917 delete fastTrack;
920 void nsRetrievalContextWayland::TransferFastTrackClipboard(
921 ClipboardDataType aDataType, int aClipboardRequestNumber,
922 GtkSelectionData* aSelectionData) {
923 LOGCLIP(
924 "nsRetrievalContextWayland::TransferFastTrackClipboard(), "
925 "aSelectionData = %p\n",
926 aSelectionData);
928 if (mClipboardRequestNumber != aClipboardRequestNumber) {
929 LOGCLIP(" request number does not match!\n");
930 return;
932 LOGCLIP(" request number matches\n");
934 int dataLength = gtk_selection_data_get_length(aSelectionData);
935 if (dataLength < 0) {
936 LOGCLIP(
937 " gtk_clipboard_request_contents() failed to get clipboard "
938 "data!\n");
939 ReleaseClipboardData(mClipboardData);
940 return;
943 switch (aDataType) {
944 case CLIPBOARD_TARGETS: {
945 LOGCLIP(" fastracking %d bytes of clipboard targets.\n", dataLength);
946 gint n_targets = 0;
947 GdkAtom* targets = nullptr;
949 if (!gtk_selection_data_get_targets(aSelectionData, &targets,
950 &n_targets) ||
951 !n_targets) {
952 ReleaseClipboardData(mClipboardData);
955 mClipboardData = reinterpret_cast<char*>(targets);
956 mClipboardDataLength = n_targets;
957 break;
959 case CLIPBOARD_DATA:
960 case CLIPBOARD_TEXT: {
961 LOGCLIP(" fastracking %d bytes of data.\n", dataLength);
962 mClipboardDataLength = dataLength;
963 if (dataLength > 0) {
964 mClipboardData = reinterpret_cast<char*>(
965 g_malloc(sizeof(char) * (mClipboardDataLength + 1)));
966 memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData),
967 sizeof(char) * mClipboardDataLength);
968 mClipboardData[mClipboardDataLength] = '\0';
969 LOGCLIP(" done, mClipboardData = %p\n", mClipboardData);
970 } else {
971 ReleaseClipboardData(mClipboardData);
977 GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
978 int* aTargetNum) {
979 /* If actual clipboard data is owned by us we don't need to go
980 * through Wayland but we ask Gtk+ to directly call data
981 * getter callback nsClipboard::SelectionGetEvent().
982 * see gtk_selection_convert() at gtk+/gtkselection.c.
984 GdkAtom selection = GetSelectionAtom(aWhichClipboard);
985 if (gdk_selection_owner_get(selection)) {
986 LOGCLIP(" Asking for internal clipboard content.\n");
987 mClipboardRequestNumber++;
988 gtk_clipboard_request_contents(
989 gtk_clipboard_get(selection), gdk_atom_intern("TARGETS", FALSE),
990 wayland_clipboard_contents_received,
991 new FastTrackClipboard(CLIPBOARD_TARGETS, mClipboardRequestNumber,
992 this));
993 *aTargetNum = mClipboardDataLength;
994 GdkAtom* targets = static_cast<GdkAtom*>((void*)mClipboardData);
995 // We don't hold the target list internally but we transfer the ownership.
996 mClipboardData = nullptr;
997 mClipboardDataLength = 0;
998 return targets;
1001 if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) {
1002 if (mClipboardOffer) {
1003 return mClipboardOffer->GetTargets(aTargetNum);
1005 } else {
1006 if (mPrimaryOffer) {
1007 return mPrimaryOffer->GetTargets(aTargetNum);
1011 *aTargetNum = 0;
1012 return nullptr;
1015 const char* nsRetrievalContextWayland::GetClipboardData(
1016 const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) {
1017 NS_ASSERTION(mClipboardData == nullptr && mClipboardDataLength == 0,
1018 "Looks like we're leaking clipboard data here!");
1020 LOGCLIP("nsRetrievalContextWayland::GetClipboardData [%p] mime %s\n", this,
1021 aMimeType);
1023 /* If actual clipboard data is owned by us we don't need to go
1024 * through Wayland but we ask Gtk+ to directly call data
1025 * getter callback nsClipboard::SelectionGetEvent().
1026 * see gtk_selection_convert() at gtk+/gtkselection.c.
1028 GdkAtom selection = GetSelectionAtom(aWhichClipboard);
1029 if (gdk_selection_owner_get(selection)) {
1030 LOGCLIP(" Asking for internal clipboard content.\n");
1031 mClipboardRequestNumber++;
1032 gtk_clipboard_request_contents(
1033 gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE),
1034 wayland_clipboard_contents_received,
1035 new FastTrackClipboard(CLIPBOARD_DATA, mClipboardRequestNumber, this));
1036 } else {
1037 LOGCLIP(" Asking for remote clipboard content.\n");
1038 RefPtr<DataOffer> dataOffer =
1039 (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
1040 if (!dataOffer) {
1041 // Something went wrong. We're requested to provide clipboard data
1042 // but we haven't got any from wayland.
1043 LOGCLIP(" We're missing dataOffer! mClipboardData = null\n");
1044 mClipboardData = nullptr;
1045 mClipboardDataLength = 0;
1046 } else {
1047 LOGCLIP(" Getting clipboard data from compositor, MIME %s\n", aMimeType);
1048 mClipboardData =
1049 mAsyncDataGetter
1050 ? dataOffer->GetDataAsync(aMimeType, &mClipboardDataLength)
1051 : dataOffer->GetData(aMimeType, &mClipboardDataLength);
1052 LOGCLIP(" Got %d bytes of data, mClipboardData = %p\n",
1053 mClipboardDataLength, mClipboardData);
1057 *aContentLength = mClipboardDataLength;
1058 return reinterpret_cast<const char*>(mClipboardData);
1061 const char* nsRetrievalContextWayland::GetClipboardText(
1062 int32_t aWhichClipboard) {
1063 GdkAtom selection = GetSelectionAtom(aWhichClipboard);
1065 LOGCLIP("nsRetrievalContextWayland::GetClipboardText [%p], clipboard %s\n",
1066 this, (selection == GDK_SELECTION_PRIMARY) ? "Primary" : "Selection");
1068 const auto& dataOffer =
1069 (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
1070 if (!dataOffer) {
1071 LOGCLIP(" We're missing data offer!\n");
1072 return nullptr;
1075 for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) {
1076 if (dataOffer->HasTarget(sTextMimeTypes[i])) {
1077 LOGCLIP(" We have %s MIME type in clipboard, ask for it.\n",
1078 sTextMimeTypes[i]);
1079 uint32_t unused;
1080 return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, &unused);
1084 LOGCLIP(" There isn't text MIME type in clipboard!\n");
1085 return nullptr;
1088 void nsRetrievalContextWayland::ReleaseClipboardData(
1089 const char* aClipboardData) {
1090 LOGCLIP("nsRetrievalContextWayland::ReleaseClipboardData [%p]\n",
1091 aClipboardData);
1092 if (aClipboardData != mClipboardData) {
1093 NS_WARNING("Wayland clipboard: Releasing unknown clipboard data!");
1095 g_free((void*)mClipboardData);
1096 mClipboardDataLength = 0;
1097 mClipboardData = nullptr;