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/. */
7 
8 #include "mozilla/ArrayUtils.h"
9 
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 
26 #include <gtk/gtk.h>
27 #include <poll.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 
33 using namespace mozilla;
34 using namespace mozilla::widget;
35 
36 const char* nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] = {
37     "text/plain;charset=utf-8", "UTF8_STRING", "COMPOUND_TEXT"};
38 
wl_to_gdk_actions(uint32_t dnd_actions)39 static inline GdkDragAction wl_to_gdk_actions(uint32_t dnd_actions) {
40   GdkDragAction actions = GdkDragAction(0);
41 
42   if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
43     actions = GdkDragAction(actions | GDK_ACTION_COPY);
44   if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
45     actions = GdkDragAction(actions | GDK_ACTION_MOVE);
46 
47   return actions;
48 }
49 
gdk_to_wl_actions(GdkDragAction action)50 static inline uint32_t gdk_to_wl_actions(GdkDragAction action) {
51   uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
52 
53   if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE))
54     dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
55   if (action & GDK_ACTION_MOVE)
56     dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
57 
58   return dnd_actions;
59 }
60 
get_gtk_widget_for_wl_surface(struct wl_surface * surface)61 static GtkWidget* get_gtk_widget_for_wl_surface(struct wl_surface* surface) {
62   GdkWindow* gdkParentWindow =
63       static_cast<GdkWindow*>(wl_surface_get_user_data(surface));
64 
65   gpointer user_data = nullptr;
66   gdk_window_get_user_data(gdkParentWindow, &user_data);
67 
68   return GTK_WIDGET(user_data);
69 }
70 
AddMIMEType(const char * aMimeType)71 void DataOffer::AddMIMEType(const char* aMimeType) {
72   GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
73   mTargetMIMETypes.AppendElement(atom);
74 }
75 
GetTargets(int * aTargetNum)76 GdkAtom* DataOffer::GetTargets(int* aTargetNum) {
77   int length = mTargetMIMETypes.Length();
78   if (!length) {
79     *aTargetNum = 0;
80     return nullptr;
81   }
82 
83   GdkAtom* targetList =
84       reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * length));
85   for (int32_t j = 0; j < length; j++) {
86     targetList[j] = mTargetMIMETypes[j];
87   }
88 
89   *aTargetNum = length;
90   return targetList;
91 }
92 
HasTarget(const char * aMimeType)93 bool DataOffer::HasTarget(const char* aMimeType) {
94   int length = mTargetMIMETypes.Length();
95   for (int32_t j = 0; j < length; j++) {
96     if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE)) {
97       LOGCLIP(("DataOffer::HasTarget() we have mime %s\n", aMimeType));
98       return true;
99     }
100   }
101   LOGCLIP(("DataOffer::HasTarget() missing mime %s\n", aMimeType));
102   return false;
103 }
104 
GetData(wl_display * aDisplay,const char * aMimeType,uint32_t * aContentLength)105 char* DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
106                          uint32_t* aContentLength) {
107   LOGCLIP(("DataOffer::GetData() mime %s\n", aMimeType));
108 
109   int pipe_fd[2];
110   if (pipe(pipe_fd) == -1) {
111     return nullptr;
112   }
113 
114   if (!RequestDataTransfer(aMimeType, pipe_fd[1])) {
115     NS_WARNING("DataOffer::RequestDataTransfer() failed!");
116     close(pipe_fd[0]);
117     close(pipe_fd[1]);
118     return nullptr;
119   }
120 
121   close(pipe_fd[1]);
122   wl_display_flush(aDisplay);
123 
124   GIOChannel* channel = g_io_channel_unix_new(pipe_fd[0]);
125   GError* error = nullptr;
126   char* clipboardData = nullptr;
127 
128   g_io_channel_set_encoding(channel, nullptr, &error);
129   if (!error) {
130     gsize length = 0;
131     g_io_channel_read_to_end(channel, &clipboardData, &length, &error);
132     if (length == 0) {
133       // We don't have valid clipboard data although
134       // g_io_channel_read_to_end() allocated clipboardData for us.
135       // Release it now and return nullptr to indicate
136       // we don't have reqested data flavour.
137       g_free((void*)clipboardData);
138       clipboardData = nullptr;
139     }
140     *aContentLength = length;
141   }
142 
143   if (error) {
144     NS_WARNING(
145         nsPrintfCString("Unexpected error when reading clipboard data: %s",
146                         error->message)
147             .get());
148     g_error_free(error);
149   }
150 
151   g_io_channel_unref(channel);
152   close(pipe_fd[0]);
153 
154   LOGCLIP(("  Got clipboard data length %d\n", *aContentLength));
155   return clipboardData;
156 }
157 
RequestDataTransfer(const char * aMimeType,int fd)158 bool WaylandDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
159   LOGCLIP(
160       ("WaylandDataOffer::RequestDataTransfer MIME %s FD %d\n", aMimeType, fd));
161   if (mWaylandDataOffer) {
162     wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd);
163     return true;
164   }
165 
166   return false;
167 }
168 
DragOfferAccept(const char * aMimeType,uint32_t aTime)169 void WaylandDataOffer::DragOfferAccept(const char* aMimeType, uint32_t aTime) {
170   LOGDRAG(("WaylandDataOffer::DragOfferAccept MIME %s aTime %d\n", aMimeType,
171            aTime));
172   wl_data_offer_accept(mWaylandDataOffer, aTime, aMimeType);
173 }
174 
175 /* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c
176  * here.
177  */
SetDragStatus(GdkDragAction aPreferredAction,uint32_t aTime)178 void WaylandDataOffer::SetDragStatus(GdkDragAction aPreferredAction,
179                                      uint32_t aTime) {
180   uint32_t preferredAction = gdk_to_wl_actions(aPreferredAction);
181   uint32_t allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
182 
183   LOGDRAG(("WaylandDataOffer::SetDragStatus aPreferredAction %d\n",
184            aPreferredAction));
185 
186   /* We only don't choose a preferred action if we don't accept any.
187    * If we do accept any, it is currently alway copy and move
188    */
189   if (preferredAction != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) {
190     allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
191                  WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
192   }
193 
194   wl_data_offer_set_actions(mWaylandDataOffer, allActions, preferredAction);
195 
196   /* Workaround Wayland D&D architecture here. To get the data_device_drop()
197      signal (which routes to nsDragService::GetData() call) we need to
198      accept at least one mime type before data_device_leave().
199 
200      Real wl_data_offer_accept() for actualy requested data mime type is
201      called from nsDragService::GetData().
202   */
203   if (mTargetMIMETypes[0]) {
204     wl_data_offer_accept(mWaylandDataOffer, aTime,
205                          gdk_atom_name(mTargetMIMETypes[0]));
206   }
207 }
208 
SetSelectedDragAction(uint32_t aWaylandAction)209 void WaylandDataOffer::SetSelectedDragAction(uint32_t aWaylandAction) {
210   mSelectedDragAction = aWaylandAction;
211 }
212 
GetSelectedDragAction()213 GdkDragAction WaylandDataOffer::GetSelectedDragAction() {
214   return wl_to_gdk_actions(mSelectedDragAction);
215 }
216 
SetAvailableDragActions(uint32_t aWaylandActions)217 void WaylandDataOffer::SetAvailableDragActions(uint32_t aWaylandActions) {
218   mAvailableDragActions = aWaylandActions;
219 }
220 
GetAvailableDragActions()221 GdkDragAction WaylandDataOffer::GetAvailableDragActions() {
222   return wl_to_gdk_actions(mAvailableDragActions);
223 }
224 
SetWaylandDragContext(nsWaylandDragContext * aDragContext)225 void WaylandDataOffer::SetWaylandDragContext(
226     nsWaylandDragContext* aDragContext) {
227   mDragContext = aDragContext;
228 }
229 
GetWaylandDragContext()230 nsWaylandDragContext* WaylandDataOffer::GetWaylandDragContext() {
231   return mDragContext;
232 }
233 
data_offer_offer(void * data,struct wl_data_offer * wl_data_offer,const char * type)234 static void data_offer_offer(void* data, struct wl_data_offer* wl_data_offer,
235                              const char* type) {
236   auto* offer = static_cast<DataOffer*>(data);
237   LOGCLIP(("Data offer %p add MIME %s\n", wl_data_offer, type));
238   offer->AddMIMEType(type);
239 }
240 
241 /* Advertise all available drag and drop actions from source.
242  * We don't use that but follow gdk_wayland_drag_context_commit_status()
243  * from gdkdnd-wayland.c here.
244  */
data_offer_source_actions(void * data,struct wl_data_offer * wl_data_offer,uint32_t source_actions)245 static void data_offer_source_actions(void* data,
246                                       struct wl_data_offer* wl_data_offer,
247                                       uint32_t source_actions) {
248   auto* offer = static_cast<WaylandDataOffer*>(data);
249   offer->SetAvailableDragActions(source_actions);
250 }
251 
252 /* Advertise recently selected drag and drop action by compositor, based
253  * on source actions and user choice (key modifiers, etc.).
254  */
data_offer_action(void * data,struct wl_data_offer * wl_data_offer,uint32_t dnd_action)255 static void data_offer_action(void* data, struct wl_data_offer* wl_data_offer,
256                               uint32_t dnd_action) {
257   auto* offer = static_cast<WaylandDataOffer*>(data);
258   offer->SetSelectedDragAction(dnd_action);
259 
260   /* Mimic GTK which triggers the motion event callback */
261   nsWaylandDragContext* dropContext = offer->GetWaylandDragContext();
262   if (dropContext) {
263     uint32_t time;
264     nscoord x, y;
265     dropContext->GetLastDropInfo(&time, &x, &y);
266 
267     WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x,
268                             y, time);
269   }
270 }
271 
272 /* wl_data_offer callback description:
273  *
274  * data_offer_offer - Is called for each MIME type available at wl_data_offer.
275  * data_offer_source_actions - This event indicates the actions offered by
276  *                             the data source.
277  * data_offer_action - This event indicates the action selected by
278  *                     the compositor after matching the source/destination
279  *                     side actions.
280  */
281 static const moz_wl_data_offer_listener data_offer_listener = {
282     data_offer_offer, data_offer_source_actions, data_offer_action};
283 
WaylandDataOffer(wl_data_offer * aWaylandDataOffer)284 WaylandDataOffer::WaylandDataOffer(wl_data_offer* aWaylandDataOffer)
285     : mWaylandDataOffer(aWaylandDataOffer),
286       mDragContext(nullptr),
287       mSelectedDragAction(0),
288       mAvailableDragActions(0) {
289   wl_data_offer_add_listener(
290       mWaylandDataOffer, (struct wl_data_offer_listener*)&data_offer_listener,
291       this);
292 }
293 
~WaylandDataOffer(void)294 WaylandDataOffer::~WaylandDataOffer(void) {
295   if (mWaylandDataOffer) {
296     wl_data_offer_destroy(mWaylandDataOffer);
297   }
298 }
299 
RequestDataTransfer(const char * aMimeType,int fd)300 bool PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
301   if (mPrimaryDataOfferGtk) {
302     gtk_primary_selection_offer_receive(mPrimaryDataOfferGtk, aMimeType, fd);
303     return true;
304   }
305   if (mPrimaryDataOfferZwpV1) {
306     zwp_primary_selection_offer_v1_receive(mPrimaryDataOfferZwpV1, aMimeType,
307                                            fd);
308     return true;
309   }
310   return false;
311 }
312 
primary_data_offer(void * data,gtk_primary_selection_offer * primary_selection_offer,const char * mime_type)313 static void primary_data_offer(
314     void* data, gtk_primary_selection_offer* primary_selection_offer,
315     const char* mime_type) {
316   LOGCLIP(("Primary data offer %p add MIME %s\n", primary_selection_offer,
317            mime_type));
318   auto* offer = static_cast<DataOffer*>(data);
319   offer->AddMIMEType(mime_type);
320 }
321 
primary_data_offer(void * data,zwp_primary_selection_offer_v1 * primary_selection_offer,const char * mime_type)322 static void primary_data_offer(
323     void* data, zwp_primary_selection_offer_v1* primary_selection_offer,
324     const char* mime_type) {
325   LOGCLIP(("Primary data offer %p add MIME %s\n", primary_selection_offer,
326            mime_type));
327   auto* offer = static_cast<DataOffer*>(data);
328   offer->AddMIMEType(mime_type);
329 }
330 
331 /* gtk_primary_selection_offer_listener callback description:
332  *
333  * primary_data_offer - Is called for each MIME type available at
334  *                      gtk_primary_selection_offer.
335  */
336 static const struct gtk_primary_selection_offer_listener
337     primary_selection_offer_listener_gtk = {primary_data_offer};
338 
339 static const struct zwp_primary_selection_offer_v1_listener
340     primary_selection_offer_listener_zwp_v1 = {primary_data_offer};
341 
PrimaryDataOffer(gtk_primary_selection_offer * aPrimaryDataOffer)342 PrimaryDataOffer::PrimaryDataOffer(
343     gtk_primary_selection_offer* aPrimaryDataOffer)
344     : mPrimaryDataOfferGtk(aPrimaryDataOffer), mPrimaryDataOfferZwpV1(nullptr) {
345   gtk_primary_selection_offer_add_listener(
346       aPrimaryDataOffer, &primary_selection_offer_listener_gtk, this);
347 }
348 
PrimaryDataOffer(zwp_primary_selection_offer_v1 * aPrimaryDataOffer)349 PrimaryDataOffer::PrimaryDataOffer(
350     zwp_primary_selection_offer_v1* aPrimaryDataOffer)
351     : mPrimaryDataOfferGtk(nullptr), mPrimaryDataOfferZwpV1(aPrimaryDataOffer) {
352   zwp_primary_selection_offer_v1_add_listener(
353       aPrimaryDataOffer, &primary_selection_offer_listener_zwp_v1, this);
354 }
355 
~PrimaryDataOffer(void)356 PrimaryDataOffer::~PrimaryDataOffer(void) {
357   if (mPrimaryDataOfferGtk) {
358     gtk_primary_selection_offer_destroy(mPrimaryDataOfferGtk);
359   }
360   if (mPrimaryDataOfferZwpV1) {
361     zwp_primary_selection_offer_v1_destroy(mPrimaryDataOfferZwpV1);
362   }
363 }
364 
365 NS_IMPL_ISUPPORTS(nsWaylandDragContext, nsISupports);
366 
nsWaylandDragContext(WaylandDataOffer * aDataOffer,wl_display * aDisplay)367 nsWaylandDragContext::nsWaylandDragContext(WaylandDataOffer* aDataOffer,
368                                            wl_display* aDisplay)
369     : mDataOffer(aDataOffer),
370       mDisplay(aDisplay),
371       mTime(0),
372       mGtkWidget(nullptr),
373       mX(0),
374       mY(0) {
375   aDataOffer->SetWaylandDragContext(this);
376 }
377 
DropDataEnter(GtkWidget * aGtkWidget,uint32_t aTime,nscoord aX,nscoord aY)378 void nsWaylandDragContext::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime,
379                                          nscoord aX, nscoord aY) {
380   mTime = aTime;
381   mGtkWidget = aGtkWidget;
382   mX = aX;
383   mY = aY;
384 }
385 
DropMotion(uint32_t aTime,nscoord aX,nscoord aY)386 void nsWaylandDragContext::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) {
387   mTime = aTime;
388   mX = aX;
389   mY = aY;
390 }
391 
GetLastDropInfo(uint32_t * aTime,nscoord * aX,nscoord * aY)392 void nsWaylandDragContext::GetLastDropInfo(uint32_t* aTime, nscoord* aX,
393                                            nscoord* aY) {
394   *aTime = mTime;
395   *aX = mX;
396   *aY = mY;
397 }
398 
SetDragStatus(GdkDragAction aPreferredAction)399 void nsWaylandDragContext::SetDragStatus(GdkDragAction aPreferredAction) {
400   mDataOffer->SetDragStatus(aPreferredAction, mTime);
401 }
402 
GetAvailableDragActions()403 GdkDragAction nsWaylandDragContext::GetAvailableDragActions() {
404   GdkDragAction gdkAction = mDataOffer->GetSelectedDragAction();
405 
406   // We emulate gdk_drag_context_get_actions() here.
407   if (!gdkAction) {
408     gdkAction = mDataOffer->GetAvailableDragActions();
409   }
410 
411   return gdkAction;
412 }
413 
GetTargets()414 GList* nsWaylandDragContext::GetTargets() {
415   int targetNums;
416   GdkAtom* atoms = mDataOffer->GetTargets(&targetNums);
417 
418   GList* targetList = nullptr;
419   for (int i = 0; i < targetNums; i++) {
420     targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i]));
421   }
422 
423   return targetList;
424 }
425 
GetData(const char * aMimeType,uint32_t * aContentLength)426 char* nsWaylandDragContext::GetData(const char* aMimeType,
427                                     uint32_t* aContentLength) {
428   LOGDRAG(("nsWaylandDragContext::GetData %s\n", aMimeType));
429   mDataOffer->DragOfferAccept(aMimeType, mTime);
430   return mDataOffer->GetData(mDisplay, aMimeType, aContentLength);
431 }
432 
RegisterNewDataOffer(wl_data_offer * aWaylandDataOffer)433 void nsRetrievalContextWayland::RegisterNewDataOffer(
434     wl_data_offer* aWaylandDataOffer) {
435   LOGCLIP(
436       ("nsRetrievalContextWayland::RegisterNewDataOffer (wl_data_offer) %p\n",
437        aWaylandDataOffer));
438 
439   DataOffer* dataOffer = static_cast<DataOffer*>(
440       g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
441   MOZ_ASSERT(
442       dataOffer == nullptr,
443       "Registered WaylandDataOffer already exists. Wayland protocol error?");
444 
445   if (!dataOffer) {
446     dataOffer = new WaylandDataOffer(aWaylandDataOffer);
447     g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer);
448   }
449 }
450 
RegisterNewDataOffer(gtk_primary_selection_offer * aPrimaryDataOffer)451 void nsRetrievalContextWayland::RegisterNewDataOffer(
452     gtk_primary_selection_offer* aPrimaryDataOffer) {
453   LOGCLIP(("nsRetrievalContextWayland::RegisterNewDataOffer (primary) %p\n",
454            aPrimaryDataOffer));
455 
456   DataOffer* dataOffer = static_cast<DataOffer*>(
457       g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
458   MOZ_ASSERT(
459       dataOffer == nullptr,
460       "Registered PrimaryDataOffer already exists. Wayland protocol error?");
461 
462   if (!dataOffer) {
463     dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
464     g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
465   }
466 }
467 
RegisterNewDataOffer(zwp_primary_selection_offer_v1 * aPrimaryDataOffer)468 void nsRetrievalContextWayland::RegisterNewDataOffer(
469     zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
470   LOGCLIP(("nsRetrievalContextWayland::RegisterNewDataOffer (primary ZWP) %p\n",
471            aPrimaryDataOffer));
472 
473   DataOffer* dataOffer = static_cast<DataOffer*>(
474       g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
475   MOZ_ASSERT(
476       dataOffer == nullptr,
477       "Registered PrimaryDataOffer already exists. Wayland protocol error?");
478 
479   if (!dataOffer) {
480     dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
481     g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
482   }
483 }
484 
SetClipboardDataOffer(wl_data_offer * aWaylandDataOffer)485 void nsRetrievalContextWayland::SetClipboardDataOffer(
486     wl_data_offer* aWaylandDataOffer) {
487   LOGCLIP(
488       ("nsRetrievalContextWayland::SetClipboardDataOffer (wl_data_offer) %p\n",
489        aWaylandDataOffer));
490 
491   // Delete existing clipboard data offer
492   mClipboardOffer = nullptr;
493 
494   // null aWaylandDataOffer indicates that our clipboard content
495   // is no longer valid and should be release.
496   if (aWaylandDataOffer != nullptr) {
497     DataOffer* dataOffer = static_cast<DataOffer*>(
498         g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
499 #ifdef MOZ_LOGGING
500     if (!dataOffer) {
501       LOGCLIP(("    We're missing stored clipboard data offer!\n"));
502     }
503 #endif
504     if (dataOffer) {
505       g_hash_table_remove(mActiveOffers, aWaylandDataOffer);
506       mClipboardOffer = WrapUnique(dataOffer);
507     }
508   }
509 }
510 
SetPrimaryDataOffer(gtk_primary_selection_offer * aPrimaryDataOffer)511 void nsRetrievalContextWayland::SetPrimaryDataOffer(
512     gtk_primary_selection_offer* aPrimaryDataOffer) {
513   LOGCLIP(("nsRetrievalContextWayland::SetPrimaryDataOffer (primary) %p\n",
514            aPrimaryDataOffer));
515 
516   // Release any primary offer we have.
517   mPrimaryOffer = nullptr;
518 
519   // aPrimaryDataOffer can be null which means we lost
520   // the mouse selection.
521   if (aPrimaryDataOffer) {
522     DataOffer* dataOffer = static_cast<DataOffer*>(
523         g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
524 #ifdef MOZ_LOGGING
525     if (!dataOffer) {
526       LOGCLIP(("    We're missing stored primary data offer!\n"));
527     }
528 #endif
529     if (dataOffer) {
530       g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
531       mPrimaryOffer = WrapUnique(dataOffer);
532     }
533   }
534 }
535 
SetPrimaryDataOffer(zwp_primary_selection_offer_v1 * aPrimaryDataOffer)536 void nsRetrievalContextWayland::SetPrimaryDataOffer(
537     zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
538   LOGCLIP(("nsRetrievalContextWayland::SetPrimaryDataOffer (primary ZWP)%p\n",
539            aPrimaryDataOffer));
540 
541   // Release any primary offer we have.
542   mPrimaryOffer = nullptr;
543 
544   // aPrimaryDataOffer can be null which means we lost
545   // the mouse selection.
546   if (aPrimaryDataOffer) {
547     DataOffer* dataOffer = static_cast<DataOffer*>(
548         g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
549 #ifdef MOZ_LOGGING
550     if (!dataOffer) {
551       LOGCLIP(("    We're missing stored primary data offer!\n"));
552     }
553 #endif
554     if (dataOffer) {
555       g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
556       mPrimaryOffer = WrapUnique(dataOffer);
557     }
558   }
559 }
560 
AddDragAndDropDataOffer(wl_data_offer * aDropDataOffer)561 void nsRetrievalContextWayland::AddDragAndDropDataOffer(
562     wl_data_offer* aDropDataOffer) {
563   LOGDRAG(("nsRetrievalContextWayland::AddDragAndDropDataOffer %p\n",
564            aDropDataOffer));
565 
566   // Remove any existing D&D contexts.
567   mDragContext = nullptr;
568 
569   WaylandDataOffer* dataOffer = static_cast<WaylandDataOffer*>(
570       g_hash_table_lookup(mActiveOffers, aDropDataOffer));
571 #ifdef MOZ_LOGGING
572   if (!dataOffer) {
573     LOGDRAG(("    We're missing stored Drag & Drop data offer!\n"));
574   }
575 #endif
576   if (dataOffer) {
577     g_hash_table_remove(mActiveOffers, aDropDataOffer);
578     mDragContext = new nsWaylandDragContext(dataOffer, mDisplay->GetDisplay());
579   }
580 }
581 
GetDragContext(void)582 nsWaylandDragContext* nsRetrievalContextWayland::GetDragContext(void) {
583   return mDragContext;
584 }
585 
ClearDragAndDropDataOffer(void)586 void nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) {
587   LOGDRAG(("nsRetrievalContextWayland::ClearDragAndDropDataOffer()\n"));
588   mDragContext = nullptr;
589 }
590 
591 // We have a new fresh data content.
592 // We should attach listeners to it and save for further use.
data_device_data_offer(void * data,struct wl_data_device * data_device,struct wl_data_offer * offer)593 static void data_device_data_offer(void* data,
594                                    struct wl_data_device* data_device,
595                                    struct wl_data_offer* offer) {
596   LOGCLIP(("data_device_data_offer(), wl_data_offer %p\n", offer));
597   nsRetrievalContextWayland* context =
598       static_cast<nsRetrievalContextWayland*>(data);
599   context->RegisterNewDataOffer(offer);
600 }
601 
602 // The new fresh data content is clipboard.
data_device_selection(void * data,struct wl_data_device * wl_data_device,struct wl_data_offer * offer)603 static void data_device_selection(void* data,
604                                   struct wl_data_device* wl_data_device,
605                                   struct wl_data_offer* offer) {
606   LOGCLIP(("data_device_selection(), set wl_data_offer %p\n", offer));
607   nsRetrievalContextWayland* context =
608       static_cast<nsRetrievalContextWayland*>(data);
609   context->SetClipboardDataOffer(offer);
610 }
611 
612 // The new fresh wayland data content is drag and drop.
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)613 static void data_device_enter(void* data, struct wl_data_device* data_device,
614                               uint32_t time, struct wl_surface* surface,
615                               int32_t x_fixed, int32_t y_fixed,
616                               struct wl_data_offer* offer) {
617   nsRetrievalContextWayland* context =
618       static_cast<nsRetrievalContextWayland*>(data);
619   context->AddDragAndDropDataOffer(offer);
620 
621   nsWaylandDragContext* dragContext = context->GetDragContext();
622 
623   GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface);
624   if (!gtkWidget) {
625     NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!");
626     return;
627   }
628 
629   LOGDRAG(("nsWindow data_device_enter for GtkWidget %p\n", (void*)gtkWidget));
630   dragContext->DropDataEnter(gtkWidget, time, wl_fixed_to_int(x_fixed),
631                              wl_fixed_to_int(y_fixed));
632 }
633 
data_device_leave(void * data,struct wl_data_device * data_device)634 static void data_device_leave(void* data, struct wl_data_device* data_device) {
635   nsRetrievalContextWayland* context =
636       static_cast<nsRetrievalContextWayland*>(data);
637 
638   nsWaylandDragContext* dropContext = context->GetDragContext();
639   WindowDragLeaveHandler(dropContext->GetWidget());
640 
641   LOGDRAG(("nsWindow data_device_leave for GtkWidget %p\n",
642            (void*)dropContext->GetWidget()));
643   context->ClearDragAndDropDataOffer();
644 }
645 
data_device_motion(void * data,struct wl_data_device * data_device,uint32_t time,int32_t x_fixed,int32_t y_fixed)646 static void data_device_motion(void* data, struct wl_data_device* data_device,
647                                uint32_t time, int32_t x_fixed,
648                                int32_t y_fixed) {
649   nsRetrievalContextWayland* context =
650       static_cast<nsRetrievalContextWayland*>(data);
651 
652   nsWaylandDragContext* dropContext = context->GetDragContext();
653 
654   nscoord x = wl_fixed_to_int(x_fixed);
655   nscoord y = wl_fixed_to_int(y_fixed);
656   dropContext->DropMotion(time, x, y);
657 
658   LOGDRAG(("nsWindow data_device_motion for GtkWidget %p\n",
659            (void*)dropContext->GetWidget()));
660   WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
661                           time);
662 }
663 
data_device_drop(void * data,struct wl_data_device * data_device)664 static void data_device_drop(void* data, struct wl_data_device* data_device) {
665   nsRetrievalContextWayland* context =
666       static_cast<nsRetrievalContextWayland*>(data);
667   nsWaylandDragContext* dropContext = context->GetDragContext();
668 
669   uint32_t time;
670   nscoord x, y;
671   dropContext->GetLastDropInfo(&time, &x, &y);
672 
673   LOGDRAG(("nsWindow data_device_drop GtkWidget %p\n",
674            (void*)dropContext->GetWidget()));
675   WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
676                         time);
677 }
678 
679 /* wl_data_device callback description:
680  *
681  * data_device_data_offer - It's called when there's a new wl_data_offer
682  *                          available. We need to attach wl_data_offer_listener
683  *                          to it to get available MIME types.
684  *
685  * data_device_selection - It's called when the new wl_data_offer
686  *                         is a clipboard content.
687  *
688  * data_device_enter - It's called when the new wl_data_offer is a drag & drop
689  *                     content and it's tied to actual wl_surface.
690  * data_device_leave - It's called when the wl_data_offer (drag & dop) is not
691  *                     valid any more.
692  * data_device_motion - It's called when the drag and drop selection moves
693  *                      across wl_surface.
694  * data_device_drop - It's called when D&D operation is sucessfully finished
695  *                    and we can read the data from D&D.
696  *                    It's generated only if we call wl_data_offer_accept() and
697  *                    wl_data_offer_set_actions() from data_device_motion
698  *                    callback.
699  */
700 static const struct wl_data_device_listener data_device_listener = {
701     data_device_data_offer, data_device_enter, data_device_leave,
702     data_device_motion,     data_device_drop,  data_device_selection};
703 
primary_selection_data_offer(void * data,struct gtk_primary_selection_device * primary_selection_device,struct gtk_primary_selection_offer * primary_offer)704 static void primary_selection_data_offer(
705     void* data, struct gtk_primary_selection_device* primary_selection_device,
706     struct gtk_primary_selection_offer* primary_offer) {
707   LOGCLIP(("primary_selection_data_offer()\n"));
708   // create and add listener
709   nsRetrievalContextWayland* context =
710       static_cast<nsRetrievalContextWayland*>(data);
711   context->RegisterNewDataOffer(primary_offer);
712 }
713 
primary_selection_data_offer(void * data,struct zwp_primary_selection_device_v1 * primary_selection_device,struct zwp_primary_selection_offer_v1 * primary_offer)714 static void primary_selection_data_offer(
715     void* data,
716     struct zwp_primary_selection_device_v1* primary_selection_device,
717     struct zwp_primary_selection_offer_v1* primary_offer) {
718   LOGCLIP(("primary_selection_data_offer()\n"));
719   // create and add listener
720   nsRetrievalContextWayland* context =
721       static_cast<nsRetrievalContextWayland*>(data);
722   context->RegisterNewDataOffer(primary_offer);
723 }
724 
primary_selection_selection(void * data,struct gtk_primary_selection_device * primary_selection_device,struct gtk_primary_selection_offer * primary_offer)725 static void primary_selection_selection(
726     void* data, struct gtk_primary_selection_device* primary_selection_device,
727     struct gtk_primary_selection_offer* primary_offer) {
728   LOGCLIP(("primary_selection_selection()\n"));
729   nsRetrievalContextWayland* context =
730       static_cast<nsRetrievalContextWayland*>(data);
731   context->SetPrimaryDataOffer(primary_offer);
732 }
733 
primary_selection_selection(void * data,struct zwp_primary_selection_device_v1 * primary_selection_device,struct zwp_primary_selection_offer_v1 * primary_offer)734 static void primary_selection_selection(
735     void* data,
736     struct zwp_primary_selection_device_v1* primary_selection_device,
737     struct zwp_primary_selection_offer_v1* primary_offer) {
738   LOGCLIP(("primary_selection_selection()\n"));
739   nsRetrievalContextWayland* context =
740       static_cast<nsRetrievalContextWayland*>(data);
741   context->SetPrimaryDataOffer(primary_offer);
742 }
743 
744 /* gtk_primary_selection_device callback description:
745  *
746  * primary_selection_data_offer - It's called when there's a new
747  *                          gtk_primary_selection_offer available.  We need to
748  *                          attach gtk_primary_selection_offer_listener to it
749  *                          to get available MIME types.
750  *
751  * primary_selection_selection - It's called when the new
752  *                          gtk_primary_selection_offer is a primary selection
753  *                          content. It can be also called with
754  *                          gtk_primary_selection_offer = null which means
755  *                          there's no primary selection.
756  */
757 static const struct gtk_primary_selection_device_listener
758     primary_selection_device_listener_gtk = {
759         primary_selection_data_offer,
760         primary_selection_selection,
761 };
762 
763 static const struct zwp_primary_selection_device_v1_listener
764     primary_selection_device_listener_zwp_v1 = {
765         primary_selection_data_offer,
766         primary_selection_selection,
767 };
768 
HasSelectionSupport(void)769 bool nsRetrievalContextWayland::HasSelectionSupport(void) {
770   return (mDisplay->GetPrimarySelectionDeviceManagerZwpV1() != nullptr ||
771           mDisplay->GetPrimarySelectionDeviceManagerGtk() != nullptr);
772 }
773 
nsRetrievalContextWayland(void)774 nsRetrievalContextWayland::nsRetrievalContextWayland(void)
775     : mInitialized(false),
776       mDisplay(WaylandDisplayGet()),
777       mActiveOffers(g_hash_table_new(NULL, NULL)),
778       mClipboardOffer(nullptr),
779       mPrimaryOffer(nullptr),
780       mDragContext(nullptr),
781       mClipboardRequestNumber(0),
782       mClipboardData(nullptr),
783       mClipboardDataLength(0) {
784   wl_data_device* dataDevice = wl_data_device_manager_get_data_device(
785       mDisplay->GetDataDeviceManager(), mDisplay->GetSeat());
786   wl_data_device_add_listener(dataDevice, &data_device_listener, this);
787 
788   if (mDisplay->GetPrimarySelectionDeviceManagerZwpV1()) {
789     zwp_primary_selection_device_v1* primaryDataDevice =
790         zwp_primary_selection_device_manager_v1_get_device(
791             mDisplay->GetPrimarySelectionDeviceManagerZwpV1(),
792             mDisplay->GetSeat());
793     zwp_primary_selection_device_v1_add_listener(
794         primaryDataDevice, &primary_selection_device_listener_zwp_v1, this);
795   } else if (mDisplay->GetPrimarySelectionDeviceManagerGtk()) {
796     gtk_primary_selection_device* primaryDataDevice =
797         gtk_primary_selection_device_manager_get_device(
798             mDisplay->GetPrimarySelectionDeviceManagerGtk(),
799             mDisplay->GetSeat());
800     gtk_primary_selection_device_add_listener(
801         primaryDataDevice, &primary_selection_device_listener_gtk, this);
802   }
803 
804   mInitialized = true;
805 }
806 
offer_hash_remove(gpointer wl_offer,gpointer aDataOffer,gpointer user_data)807 static gboolean offer_hash_remove(gpointer wl_offer, gpointer aDataOffer,
808                                   gpointer user_data) {
809 #ifdef DEBUG
810   nsPrintfCString msg("nsRetrievalContextWayland(): leaked nsDataOffer %p\n",
811                       aDataOffer);
812   NS_WARNING(msg.get());
813 #endif
814   delete static_cast<DataOffer*>(aDataOffer);
815   return true;
816 }
817 
~nsRetrievalContextWayland(void)818 nsRetrievalContextWayland::~nsRetrievalContextWayland(void) {
819   g_hash_table_foreach_remove(mActiveOffers, offer_hash_remove, nullptr);
820   g_hash_table_destroy(mActiveOffers);
821 }
822 
823 struct FastTrackClipboard {
FastTrackClipboardFastTrackClipboard824   FastTrackClipboard(ClipboardDataType aDataType, int aClipboardRequestNumber,
825                      nsRetrievalContextWayland* aRetrievalContex)
826       : mClipboardRequestNumber(aClipboardRequestNumber),
827         mRetrievalContex(aRetrievalContex),
828         mDataType(aDataType) {}
829   int mClipboardRequestNumber;
830   nsRetrievalContextWayland* mRetrievalContex;
831   ClipboardDataType mDataType;
832 };
833 
wayland_clipboard_contents_received(GtkClipboard * clipboard,GtkSelectionData * selection_data,gpointer data)834 static void wayland_clipboard_contents_received(
835     GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) {
836   LOGCLIP(("wayland_clipboard_contents_received() selection_data = %p\n",
837            selection_data));
838   FastTrackClipboard* fastTrack = static_cast<FastTrackClipboard*>(data);
839   fastTrack->mRetrievalContex->TransferFastTrackClipboard(
840       fastTrack->mDataType, fastTrack->mClipboardRequestNumber, selection_data);
841   delete fastTrack;
842 }
843 
TransferFastTrackClipboard(ClipboardDataType aDataType,int aClipboardRequestNumber,GtkSelectionData * aSelectionData)844 void nsRetrievalContextWayland::TransferFastTrackClipboard(
845     ClipboardDataType aDataType, int aClipboardRequestNumber,
846     GtkSelectionData* aSelectionData) {
847   LOGCLIP(
848       ("nsRetrievalContextWayland::TransferFastTrackClipboard(), "
849        "aSelectionData = %p\n",
850        aSelectionData));
851 
852   if (mClipboardRequestNumber != aClipboardRequestNumber) {
853     LOGCLIP(("    request number does not match!\n"));
854     NS_WARNING("Received obsoleted clipboard data!");
855   }
856   LOGCLIP(("    request number matches\n"));
857 
858   int dataLength = gtk_selection_data_get_length(aSelectionData);
859   if (dataLength < 0) {
860     LOGCLIP(
861         ("    gtk_clipboard_request_contents() failed to get clipboard "
862          "data!\n"));
863     ReleaseClipboardData(mClipboardData);
864     return;
865   }
866 
867   switch (aDataType) {
868     case CLIPBOARD_TARGETS: {
869       LOGCLIP(("    fastracking %d bytes of clipboard targets.\n", dataLength));
870       gint n_targets = 0;
871       GdkAtom* targets = nullptr;
872 
873       if (!gtk_selection_data_get_targets(aSelectionData, &targets,
874                                           &n_targets) ||
875           !n_targets) {
876         ReleaseClipboardData(mClipboardData);
877       }
878 
879       mClipboardData = reinterpret_cast<char*>(targets);
880       mClipboardDataLength = n_targets;
881       break;
882     }
883     case CLIPBOARD_DATA:
884     case CLIPBOARD_TEXT: {
885       LOGCLIP(("    fastracking %d bytes of data.\n", dataLength));
886       mClipboardDataLength = dataLength;
887       if (dataLength > 0) {
888         mClipboardData = reinterpret_cast<char*>(
889             g_malloc(sizeof(char) * (mClipboardDataLength + 1)));
890         memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData),
891                sizeof(char) * mClipboardDataLength);
892         mClipboardData[mClipboardDataLength] = '\0';
893         LOGCLIP(("    done, mClipboardData = %p\n", mClipboardData));
894       } else {
895         ReleaseClipboardData(mClipboardData);
896       }
897     }
898   }
899 }
900 
GetTargets(int32_t aWhichClipboard,int * aTargetNum)901 GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
902                                                int* aTargetNum) {
903   /* If actual clipboard data is owned by us we don't need to go
904    * through Wayland but we ask Gtk+ to directly call data
905    * getter callback nsClipboard::SelectionGetEvent().
906    * see gtk_selection_convert() at gtk+/gtkselection.c.
907    */
908   GdkAtom selection = GetSelectionAtom(aWhichClipboard);
909   if (gdk_selection_owner_get(selection)) {
910     LOGCLIP(("  Asking for internal clipboard content.\n"));
911     mClipboardRequestNumber++;
912     gtk_clipboard_request_contents(
913         gtk_clipboard_get(selection), gdk_atom_intern("TARGETS", FALSE),
914         wayland_clipboard_contents_received,
915         new FastTrackClipboard(CLIPBOARD_TARGETS, mClipboardRequestNumber,
916                                this));
917     *aTargetNum = mClipboardDataLength;
918     GdkAtom* targets = static_cast<GdkAtom*>((void*)mClipboardData);
919     // We don't hold the target list internally but we transfer the ownership.
920     mClipboardData = nullptr;
921     mClipboardDataLength = 0;
922     return targets;
923   }
924 
925   if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) {
926     if (mClipboardOffer) {
927       return mClipboardOffer->GetTargets(aTargetNum);
928     }
929   } else {
930     if (mPrimaryOffer) {
931       return mPrimaryOffer->GetTargets(aTargetNum);
932     }
933   }
934 
935   *aTargetNum = 0;
936   return nullptr;
937 }
938 
GetClipboardData(const char * aMimeType,int32_t aWhichClipboard,uint32_t * aContentLength)939 const char* nsRetrievalContextWayland::GetClipboardData(
940     const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) {
941   NS_ASSERTION(mClipboardData == nullptr && mClipboardDataLength == 0,
942                "Looks like we're leaking clipboard data here!");
943 
944   LOGCLIP(("nsRetrievalContextWayland::GetClipboardData [%p] mime %s\n", this,
945            aMimeType));
946 
947   /* If actual clipboard data is owned by us we don't need to go
948    * through Wayland but we ask Gtk+ to directly call data
949    * getter callback nsClipboard::SelectionGetEvent().
950    * see gtk_selection_convert() at gtk+/gtkselection.c.
951    */
952   GdkAtom selection = GetSelectionAtom(aWhichClipboard);
953   if (gdk_selection_owner_get(selection)) {
954     LOGCLIP(("  Asking for internal clipboard content.\n"));
955     mClipboardRequestNumber++;
956     gtk_clipboard_request_contents(
957         gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE),
958         wayland_clipboard_contents_received,
959         new FastTrackClipboard(CLIPBOARD_DATA, mClipboardRequestNumber, this));
960   } else {
961     LOGCLIP(("  Asking for remote clipboard content.\n"));
962     const auto& dataOffer =
963         (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
964     if (!dataOffer) {
965       // Something went wrong. We're requested to provide clipboard data
966       // but we haven't got any from wayland.
967       LOGCLIP(("  We're missing dataOffer! mClipboardData = null\n"));
968       mClipboardData = nullptr;
969       mClipboardDataLength = 0;
970     } else {
971       LOGCLIP(
972           ("  Getting clipboard data from compositor, MIME %s\n", aMimeType));
973       mClipboardData = dataOffer->GetData(mDisplay->GetDisplay(), aMimeType,
974                                           &mClipboardDataLength);
975       LOGCLIP(("  Got %d bytes of data, mClipboardData = %p\n",
976                mClipboardDataLength, mClipboardData));
977     }
978   }
979 
980   *aContentLength = mClipboardDataLength;
981   return reinterpret_cast<const char*>(mClipboardData);
982 }
983 
GetClipboardText(int32_t aWhichClipboard)984 const char* nsRetrievalContextWayland::GetClipboardText(
985     int32_t aWhichClipboard) {
986   GdkAtom selection = GetSelectionAtom(aWhichClipboard);
987 
988   LOGCLIP(("nsRetrievalContextWayland::GetClipboardText [%p], clipboard %s\n",
989            this,
990            (selection == GDK_SELECTION_PRIMARY) ? "Primary" : "Selection"));
991 
992   const auto& dataOffer =
993       (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
994   if (!dataOffer) {
995     LOGCLIP(("  We're missing data offer!\n"));
996     return nullptr;
997   }
998 
999   for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) {
1000     if (dataOffer->HasTarget(sTextMimeTypes[i])) {
1001       LOGCLIP(("  We have %s MIME type in clipboard, ask for it.\n",
1002                sTextMimeTypes[i]));
1003       uint32_t unused;
1004       return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, &unused);
1005     }
1006   }
1007 
1008   LOGCLIP(("  There isn't text MIME type in clipboard!\n"));
1009   return nullptr;
1010 }
1011 
ReleaseClipboardData(const char * aClipboardData)1012 void nsRetrievalContextWayland::ReleaseClipboardData(
1013     const char* aClipboardData) {
1014   LOGCLIP(("nsRetrievalContextWayland::ReleaseClipboardData [%p]\n",
1015            aClipboardData));
1016   if (aClipboardData != mClipboardData) {
1017     NS_WARNING("Wayland clipboard: Releasing unknown clipboard data!");
1018   }
1019   g_free((void*)mClipboardData);
1020   mClipboardDataLength = 0;
1021   mClipboardData = nullptr;
1022 }
1023