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