Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "HeadlessClipboard.h"
13 : #include "nsSupportsPrimitives.h"
14 : #include "nsString.h"
15 : #include "nsReadableUtils.h"
16 : #include "nsXPIDLString.h"
17 : #include "nsPrimitiveHelpers.h"
18 : #include "nsIServiceManager.h"
19 : #include "nsImageToPixbuf.h"
20 : #include "nsStringStream.h"
21 : #include "nsIObserverService.h"
22 : #include "mozilla/Services.h"
23 : #include "mozilla/RefPtr.h"
24 : #include "mozilla/ClearOnShutdown.h"
25 : #include "mozilla/TimeStamp.h"
26 :
27 : #include "imgIContainer.h"
28 :
29 : #include <gtk/gtk.h>
30 :
31 : // For manipulation of the X event queue
32 : #include <X11/Xlib.h>
33 : #include <gdk/gdkx.h>
34 : #include <sys/time.h>
35 : #include <sys/types.h>
36 : #include <errno.h>
37 : #include <unistd.h>
38 : #include "X11UndefineNone.h"
39 :
40 : #include "mozilla/Encoding.h"
41 :
42 : #include "gfxPlatform.h"
43 :
44 : using namespace mozilla;
45 :
46 : // Callback when someone asks us for the data
47 : void
48 : clipboard_get_cb(GtkClipboard *aGtkClipboard,
49 : GtkSelectionData *aSelectionData,
50 : guint info,
51 : gpointer user_data);
52 :
53 : // Callback when someone asks us to clear a clipboard
54 : void
55 : clipboard_clear_cb(GtkClipboard *aGtkClipboard,
56 : gpointer user_data);
57 :
58 : static void
59 : ConvertHTMLtoUCS2 (guchar *data,
60 : int32_t dataLength,
61 : char16_t **unicodeData,
62 : int32_t &outUnicodeLen);
63 :
64 : static void
65 : GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str);
66 :
67 :
68 : // Our own versions of gtk_clipboard_wait_for_contents and
69 : // gtk_clipboard_wait_for_text, which don't run the event loop while
70 : // waiting for the data. This prevents a lot of problems related to
71 : // dispatching events at unexpected times.
72 :
73 : static GtkSelectionData *
74 : wait_for_contents (GtkClipboard *clipboard, GdkAtom target);
75 :
76 : static gchar *
77 : wait_for_text (GtkClipboard *clipboard);
78 :
79 : static GdkFilterReturn
80 : selection_request_filter (GdkXEvent *gdk_xevent,
81 : GdkEvent *event,
82 : gpointer data);
83 :
84 : namespace mozilla {
85 : namespace clipboard {
86 3 : StaticRefPtr<nsIClipboard> sInstance;
87 : }
88 : }
89 : /* static */ already_AddRefed<nsIClipboard>
90 1 : nsClipboard::GetInstance()
91 : {
92 : using namespace mozilla::clipboard;
93 :
94 1 : if (!sInstance) {
95 1 : if (gfxPlatform::IsHeadless()) {
96 0 : sInstance = new widget::HeadlessClipboard();
97 : } else {
98 2 : RefPtr<nsClipboard> clipboard = new nsClipboard();
99 1 : nsresult rv = clipboard->Init();
100 1 : if (NS_FAILED(rv)) {
101 0 : return nullptr;
102 : }
103 1 : sInstance = clipboard.forget();
104 : }
105 1 : ClearOnShutdown(&sInstance);
106 : }
107 :
108 2 : RefPtr<nsIClipboard> service = sInstance.get();
109 1 : return service.forget();
110 : }
111 :
112 1 : nsClipboard::nsClipboard()
113 : {
114 1 : }
115 :
116 0 : nsClipboard::~nsClipboard()
117 : {
118 : // We have to clear clipboard before gdk_display_close() call.
119 : // See bug 531580 for details.
120 0 : if (mGlobalTransferable) {
121 0 : gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
122 : }
123 0 : if (mSelectionTransferable) {
124 0 : gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
125 : }
126 0 : }
127 :
128 37 : NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
129 :
130 : nsresult
131 1 : nsClipboard::Init(void)
132 : {
133 2 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
134 1 : if (!os)
135 0 : return NS_ERROR_FAILURE;
136 :
137 1 : os->AddObserver(this, "quit-application", false);
138 :
139 : // A custom event filter to workaround attempting to dereference a null
140 : // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
141 : #if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)
142 1 : if (gtk_check_version(3, 11, 3))
143 0 : gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
144 : #endif
145 :
146 1 : return NS_OK;
147 : }
148 :
149 : NS_IMETHODIMP
150 0 : nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
151 : {
152 0 : if (strcmp(aTopic, "quit-application") == 0) {
153 : // application is going to quit, save clipboard content
154 0 : Store();
155 0 : gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
156 : }
157 0 : return NS_OK;
158 : }
159 :
160 : nsresult
161 0 : nsClipboard::Store(void)
162 : {
163 : // Ask the clipboard manager to store the current clipboard content
164 0 : if (mGlobalTransferable) {
165 0 : GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
166 0 : gtk_clipboard_store(clipboard);
167 : }
168 0 : return NS_OK;
169 : }
170 :
171 : NS_IMETHODIMP
172 0 : nsClipboard::SetData(nsITransferable *aTransferable,
173 : nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
174 : {
175 : // See if we can short cut
176 0 : if ((aWhichClipboard == kGlobalClipboard &&
177 0 : aTransferable == mGlobalTransferable.get() &&
178 0 : aOwner == mGlobalOwner.get()) ||
179 0 : (aWhichClipboard == kSelectionClipboard &&
180 0 : aTransferable == mSelectionTransferable.get() &&
181 0 : aOwner == mSelectionOwner.get())) {
182 0 : return NS_OK;
183 : }
184 :
185 : // Clear out the clipboard in order to set the new data
186 0 : EmptyClipboard(aWhichClipboard);
187 :
188 : // List of suported targets
189 0 : GtkTargetList *list = gtk_target_list_new(nullptr, 0);
190 :
191 : // Get the types of supported flavors
192 0 : nsCOMPtr<nsIArray> flavors;
193 :
194 : nsresult rv =
195 0 : aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
196 0 : if (!flavors || NS_FAILED(rv))
197 0 : return NS_ERROR_FAILURE;
198 :
199 : // Add all the flavors to this widget's supported type.
200 0 : bool imagesAdded = false;
201 : uint32_t count;
202 0 : flavors->GetLength(&count);
203 0 : for (uint32_t i=0; i < count; i++) {
204 0 : nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavors, i);
205 :
206 0 : if (flavor) {
207 0 : nsXPIDLCString flavorStr;
208 0 : flavor->ToString(getter_Copies(flavorStr));
209 :
210 : // special case text/unicode since we can handle all of
211 : // the string types
212 0 : if (!strcmp(flavorStr, kUnicodeMime)) {
213 0 : gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
214 0 : gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
215 0 : gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
216 0 : gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
217 0 : continue;
218 : }
219 :
220 0 : if (flavorStr.EqualsLiteral(kNativeImageMime) ||
221 0 : flavorStr.EqualsLiteral(kPNGImageMime) ||
222 0 : flavorStr.EqualsLiteral(kJPEGImageMime) ||
223 0 : flavorStr.EqualsLiteral(kJPGImageMime) ||
224 0 : flavorStr.EqualsLiteral(kGIFImageMime)) {
225 : // don't bother adding image targets twice
226 0 : if (!imagesAdded) {
227 : // accept any writable image type
228 0 : gtk_target_list_add_image_targets(list, 0, TRUE);
229 0 : imagesAdded = true;
230 : }
231 0 : continue;
232 : }
233 :
234 : // Add this to our list of valid targets
235 0 : GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
236 0 : gtk_target_list_add(list, atom, 0, 0);
237 : }
238 : }
239 :
240 : // Get GTK clipboard (CLIPBOARD or PRIMARY)
241 0 : GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
242 :
243 : gint numTargets;
244 0 : GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
245 :
246 : // Set getcallback and request to store data after an application exit
247 0 : if (gtkTargets &&
248 0 : gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
249 : clipboard_get_cb, clipboard_clear_cb, this))
250 : {
251 : // We managed to set-up the clipboard so update internal state
252 : // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()
253 : // which reset our internal state
254 0 : if (aWhichClipboard == kSelectionClipboard) {
255 0 : mSelectionOwner = aOwner;
256 0 : mSelectionTransferable = aTransferable;
257 : }
258 : else {
259 0 : mGlobalOwner = aOwner;
260 0 : mGlobalTransferable = aTransferable;
261 0 : gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
262 : }
263 :
264 0 : rv = NS_OK;
265 : }
266 : else {
267 0 : rv = NS_ERROR_FAILURE;
268 : }
269 :
270 0 : gtk_target_table_free(gtkTargets, numTargets);
271 0 : gtk_target_list_unref(list);
272 :
273 0 : return rv;
274 : }
275 :
276 : NS_IMETHODIMP
277 0 : nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
278 : {
279 0 : if (!aTransferable)
280 0 : return NS_ERROR_FAILURE;
281 :
282 : GtkClipboard *clipboard;
283 0 : clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
284 :
285 0 : guchar *data = nullptr;
286 0 : gint length = 0;
287 0 : bool foundData = false;
288 0 : nsAutoCString foundFlavor;
289 :
290 : // Get a list of flavors this transferable can import
291 0 : nsCOMPtr<nsIArray> flavors;
292 : nsresult rv;
293 0 : rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
294 0 : if (!flavors || NS_FAILED(rv))
295 0 : return NS_ERROR_FAILURE;
296 :
297 : uint32_t count;
298 0 : flavors->GetLength(&count);
299 0 : for (uint32_t i=0; i < count; i++) {
300 0 : nsCOMPtr<nsISupportsCString> currentFlavor;
301 0 : currentFlavor = do_QueryElementAt(flavors, i);
302 :
303 0 : if (currentFlavor) {
304 0 : nsXPIDLCString flavorStr;
305 0 : currentFlavor->ToString(getter_Copies(flavorStr));
306 :
307 : // Special case text/unicode since we can convert any
308 : // string into text/unicode
309 0 : if (!strcmp(flavorStr, kUnicodeMime)) {
310 0 : gchar* new_text = wait_for_text(clipboard);
311 0 : if (new_text) {
312 : // Convert utf-8 into our unicode format.
313 0 : NS_ConvertUTF8toUTF16 ucs2string(new_text);
314 0 : data = (guchar *)ToNewUnicode(ucs2string);
315 0 : length = ucs2string.Length() * 2;
316 0 : g_free(new_text);
317 0 : foundData = true;
318 0 : foundFlavor = kUnicodeMime;
319 0 : break;
320 : }
321 : // If the type was text/unicode and we couldn't get
322 : // text off the clipboard, run the next loop
323 : // iteration.
324 0 : continue;
325 : }
326 :
327 : // For images, we must wrap the data in an nsIInputStream then return instead of break,
328 : // because that code below won't help us.
329 0 : if (!strcmp(flavorStr, kJPEGImageMime) ||
330 0 : !strcmp(flavorStr, kJPGImageMime) ||
331 0 : !strcmp(flavorStr, kPNGImageMime) ||
332 0 : !strcmp(flavorStr, kGIFImageMime)) {
333 : // Emulate support for image/jpg
334 0 : if (!strcmp(flavorStr, kJPGImageMime)) {
335 0 : flavorStr.Assign(kJPEGImageMime);
336 : }
337 :
338 0 : GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
339 :
340 0 : GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
341 0 : if (!selectionData)
342 0 : continue;
343 :
344 0 : nsCOMPtr<nsIInputStream> byteStream;
345 0 : NS_NewByteInputStream(getter_AddRefs(byteStream),
346 0 : (const char*)gtk_selection_data_get_data(selectionData),
347 : gtk_selection_data_get_length(selectionData),
348 0 : NS_ASSIGNMENT_COPY);
349 0 : aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
350 0 : gtk_selection_data_free(selectionData);
351 0 : return NS_OK;
352 : }
353 :
354 : // Get the atom for this type and try to request it off
355 : // the clipboard.
356 0 : GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
357 : GtkSelectionData *selectionData;
358 0 : selectionData = wait_for_contents(clipboard, atom);
359 0 : if (selectionData) {
360 0 : const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
361 0 : length = gtk_selection_data_get_length(selectionData);
362 : // Special case text/html since we can convert into UCS2
363 0 : if (!strcmp(flavorStr, kHTMLMime)) {
364 0 : char16_t* htmlBody= nullptr;
365 0 : int32_t htmlBodyLen = 0;
366 : // Convert text/html into our unicode format
367 : ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
368 0 : &htmlBody, htmlBodyLen);
369 : // Try next data format?
370 0 : if (!htmlBodyLen)
371 0 : continue;
372 0 : data = (guchar *)htmlBody;
373 0 : length = htmlBodyLen * 2;
374 : } else {
375 0 : data = (guchar *)moz_xmalloc(length);
376 0 : if (!data)
377 0 : break;
378 0 : memcpy(data, clipboardData, length);
379 : }
380 0 : gtk_selection_data_free(selectionData);
381 0 : foundData = true;
382 0 : foundFlavor = flavorStr;
383 0 : break;
384 : }
385 : }
386 : }
387 :
388 0 : if (foundData) {
389 0 : nsCOMPtr<nsISupports> wrapper;
390 0 : nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
391 : data, length,
392 0 : getter_AddRefs(wrapper));
393 0 : aTransferable->SetTransferData(foundFlavor.get(),
394 0 : wrapper, length);
395 : }
396 :
397 0 : if (data)
398 0 : free(data);
399 :
400 0 : return NS_OK;
401 : }
402 :
403 : NS_IMETHODIMP
404 0 : nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
405 : {
406 0 : if (aWhichClipboard == kSelectionClipboard) {
407 0 : if (mSelectionOwner) {
408 0 : mSelectionOwner->LosingOwnership(mSelectionTransferable);
409 0 : mSelectionOwner = nullptr;
410 : }
411 0 : mSelectionTransferable = nullptr;
412 : }
413 : else {
414 0 : if (mGlobalOwner) {
415 0 : mGlobalOwner->LosingOwnership(mGlobalTransferable);
416 0 : mGlobalOwner = nullptr;
417 : }
418 0 : mGlobalTransferable = nullptr;
419 : }
420 :
421 0 : return NS_OK;
422 : }
423 :
424 : NS_IMETHODIMP
425 0 : nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
426 : int32_t aWhichClipboard, bool *_retval)
427 : {
428 0 : if (!aFlavorList || !_retval)
429 0 : return NS_ERROR_NULL_POINTER;
430 :
431 0 : *_retval = false;
432 :
433 : GtkSelectionData *selection_data =
434 0 : GetTargets(GetSelectionAtom(aWhichClipboard));
435 0 : if (!selection_data)
436 0 : return NS_OK;
437 :
438 0 : gint n_targets = 0;
439 0 : GdkAtom *targets = nullptr;
440 :
441 0 : if (!gtk_selection_data_get_targets(selection_data,
442 0 : &targets, &n_targets) ||
443 0 : !n_targets)
444 0 : return NS_OK;
445 :
446 : // Walk through the provided types and try to match it to a
447 : // provided type.
448 0 : for (uint32_t i = 0; i < aLength && !*_retval; i++) {
449 : // We special case text/unicode here.
450 0 : if (!strcmp(aFlavorList[i], kUnicodeMime) &&
451 0 : gtk_selection_data_targets_include_text(selection_data)) {
452 0 : *_retval = true;
453 0 : break;
454 : }
455 :
456 0 : for (int32_t j = 0; j < n_targets; j++) {
457 0 : gchar *atom_name = gdk_atom_name(targets[j]);
458 0 : if (!atom_name)
459 0 : continue;
460 :
461 0 : if (!strcmp(atom_name, aFlavorList[i]))
462 0 : *_retval = true;
463 :
464 : // X clipboard supports image/jpeg, but we want to emulate support
465 : // for image/jpg as well
466 0 : if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
467 0 : *_retval = true;
468 :
469 0 : g_free(atom_name);
470 :
471 0 : if (*_retval)
472 0 : break;
473 : }
474 : }
475 0 : gtk_selection_data_free(selection_data);
476 0 : g_free(targets);
477 :
478 0 : return NS_OK;
479 : }
480 :
481 : NS_IMETHODIMP
482 4 : nsClipboard::SupportsSelectionClipboard(bool *_retval)
483 : {
484 4 : *_retval = true; // yeah, unix supports the selection clipboard
485 4 : return NS_OK;
486 : }
487 :
488 : NS_IMETHODIMP
489 2 : nsClipboard::SupportsFindClipboard(bool* _retval)
490 : {
491 2 : *_retval = false;
492 2 : return NS_OK;
493 : }
494 :
495 : /* static */
496 : GdkAtom
497 0 : nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
498 : {
499 0 : if (aWhichClipboard == kGlobalClipboard)
500 0 : return GDK_SELECTION_CLIPBOARD;
501 :
502 0 : return GDK_SELECTION_PRIMARY;
503 : }
504 :
505 : /* static */
506 : GtkSelectionData *
507 0 : nsClipboard::GetTargets(GdkAtom aWhichClipboard)
508 : {
509 0 : GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
510 0 : return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
511 : }
512 :
513 : nsITransferable *
514 0 : nsClipboard::GetTransferable(int32_t aWhichClipboard)
515 : {
516 : nsITransferable *retval;
517 :
518 0 : if (aWhichClipboard == kSelectionClipboard)
519 0 : retval = mSelectionTransferable.get();
520 : else
521 0 : retval = mGlobalTransferable.get();
522 :
523 0 : return retval;
524 : }
525 :
526 : void
527 0 : nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard,
528 : GtkSelectionData *aSelectionData)
529 : {
530 : // Someone has asked us to hand them something. The first thing
531 : // that we want to do is see if that something includes text. If
532 : // it does, try to give it text/unicode after converting it to
533 : // utf-8.
534 :
535 : int32_t whichClipboard;
536 :
537 : // which clipboard?
538 0 : GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
539 0 : if (selection == GDK_SELECTION_PRIMARY)
540 0 : whichClipboard = kSelectionClipboard;
541 0 : else if (selection == GDK_SELECTION_CLIPBOARD)
542 0 : whichClipboard = kGlobalClipboard;
543 : else
544 0 : return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
545 :
546 0 : nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
547 0 : if (!trans) {
548 : // We have nothing to serve
549 : #ifdef DEBUG_CLIPBOARD
550 : printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
551 : whichClipboard == kSelectionClipboard ? "Selection" : "Global");
552 : #endif
553 0 : return;
554 : }
555 :
556 : nsresult rv;
557 0 : nsCOMPtr<nsISupports> item;
558 : uint32_t len;
559 :
560 0 : GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
561 :
562 : // Check to see if the selection data includes any of the string
563 : // types that we support.
564 0 : if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
565 0 : selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
566 0 : selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
567 0 : selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
568 : // Try to convert our internal type into a text string. Get
569 : // the transferable for this clipboard and try to get the
570 : // text/unicode type for it.
571 0 : rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
572 0 : &len);
573 0 : if (!item || NS_FAILED(rv))
574 0 : return;
575 :
576 0 : nsCOMPtr<nsISupportsString> wideString;
577 0 : wideString = do_QueryInterface(item);
578 0 : if (!wideString)
579 0 : return;
580 :
581 0 : nsAutoString ucs2string;
582 0 : wideString->GetData(ucs2string);
583 0 : char *utf8string = ToNewUTF8String(ucs2string);
584 0 : if (!utf8string)
585 0 : return;
586 :
587 0 : gtk_selection_data_set_text (aSelectionData, utf8string,
588 0 : strlen(utf8string));
589 :
590 0 : free(utf8string);
591 0 : return;
592 : }
593 :
594 : // Check to see if the selection data is an image type
595 0 : if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
596 : // Look through our transfer data for the image
597 : static const char* const imageMimeTypes[] = {
598 : kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
599 0 : nsCOMPtr<nsISupports> imageItem;
600 0 : nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
601 0 : for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
602 0 : rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem), &len);
603 0 : ptrPrimitive = do_QueryInterface(imageItem);
604 : }
605 0 : if (!ptrPrimitive)
606 0 : return;
607 :
608 0 : nsCOMPtr<nsISupports> primitiveData;
609 0 : ptrPrimitive->GetData(getter_AddRefs(primitiveData));
610 0 : nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
611 0 : if (!image) // Not getting an image for an image mime type!?
612 0 : return;
613 :
614 0 : GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
615 0 : if (!pixbuf)
616 0 : return;
617 :
618 0 : gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
619 0 : g_object_unref(pixbuf);
620 0 : return;
621 : }
622 :
623 : // Try to match up the selection data target to something our
624 : // transferable provides.
625 0 : gchar *target_name = gdk_atom_name(selectionTarget);
626 0 : if (!target_name)
627 0 : return;
628 :
629 0 : rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
630 : // nothing found?
631 0 : if (!item || NS_FAILED(rv)) {
632 0 : g_free(target_name);
633 0 : return;
634 : }
635 :
636 0 : void *primitive_data = nullptr;
637 0 : nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
638 0 : &primitive_data, len);
639 :
640 0 : if (primitive_data) {
641 : // Check to see if the selection data is text/html
642 0 : if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
643 : /*
644 : * "text/html" can be encoded UCS2. It is recommended that
645 : * documents transmitted as UCS2 always begin with a ZERO-WIDTH
646 : * NON-BREAKING SPACE character (hexadecimal FEFF, also called
647 : * Byte Order Mark (BOM)). Adding BOM can help other app to
648 : * detect mozilla use UCS2 encoding when copy-paste.
649 : */
650 : guchar *buffer = (guchar *)
651 0 : moz_xmalloc((len * sizeof(guchar)) + sizeof(char16_t));
652 0 : if (!buffer)
653 0 : return;
654 0 : char16_t prefix = 0xFEFF;
655 0 : memcpy(buffer, &prefix, sizeof(prefix));
656 0 : memcpy(buffer + sizeof(prefix), primitive_data, len);
657 0 : free((guchar *)primitive_data);
658 0 : primitive_data = (guchar *)buffer;
659 0 : len += sizeof(prefix);
660 : }
661 :
662 0 : gtk_selection_data_set(aSelectionData, selectionTarget,
663 : 8, /* 8 bits in a unit */
664 0 : (const guchar *)primitive_data, len);
665 0 : free(primitive_data);
666 : }
667 :
668 0 : g_free(target_name);
669 :
670 : }
671 :
672 : void
673 0 : nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
674 : {
675 : int32_t whichClipboard;
676 :
677 : // which clipboard?
678 0 : if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
679 0 : whichClipboard = kSelectionClipboard;
680 0 : else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
681 0 : whichClipboard = kGlobalClipboard;
682 : else
683 0 : return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
684 :
685 0 : EmptyClipboard(whichClipboard);
686 : }
687 :
688 : void
689 0 : clipboard_get_cb(GtkClipboard *aGtkClipboard,
690 : GtkSelectionData *aSelectionData,
691 : guint info,
692 : gpointer user_data)
693 : {
694 0 : nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
695 0 : aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
696 0 : }
697 :
698 : void
699 0 : clipboard_clear_cb(GtkClipboard *aGtkClipboard,
700 : gpointer user_data)
701 : {
702 0 : nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
703 0 : aClipboard->SelectionClearEvent(aGtkClipboard);
704 0 : }
705 :
706 : /*
707 : * when copy-paste, mozilla wants data encoded using UCS2,
708 : * other app such as StarOffice use "text/html"(RFC2854).
709 : * This function convert data(got from GTK clipboard)
710 : * to data mozilla wanted.
711 : *
712 : * data from GTK clipboard can be 3 forms:
713 : * 1. From current mozilla
714 : * "text/html", charset = utf-16
715 : * 2. From old version mozilla or mozilla-based app
716 : * content("body" only), charset = utf-16
717 : * 3. From other app who use "text/html" when copy-paste
718 : * "text/html", has "charset" info
719 : *
720 : * data : got from GTK clipboard
721 : * dataLength: got from GTK clipboard
722 : * body : pass to Mozilla
723 : * bodyLength: pass to Mozilla
724 : */
725 0 : void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
726 : char16_t** unicodeData, int32_t& outUnicodeLen)
727 : {
728 0 : nsAutoCString charset;
729 0 : GetHTMLCharset(data, dataLength, charset);// get charset of HTML
730 0 : if (charset.EqualsLiteral("UTF-16")) {//current mozilla
731 0 : outUnicodeLen = (dataLength / 2) - 1;
732 0 : *unicodeData = reinterpret_cast<char16_t*>
733 0 : (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
734 : sizeof(char16_t)));
735 0 : if (*unicodeData) {
736 0 : memcpy(*unicodeData, data + sizeof(char16_t),
737 0 : outUnicodeLen * sizeof(char16_t));
738 0 : (*unicodeData)[outUnicodeLen] = '\0';
739 : }
740 0 : } else if (charset.EqualsLiteral("UNKNOWN")) {
741 0 : outUnicodeLen = 0;
742 0 : return;
743 : } else {
744 : // app which use "text/html" to copy&paste
745 : // get the decoder
746 0 : auto encoding = Encoding::ForLabelNoReplacement(charset);
747 0 : if (!encoding) {
748 : #ifdef DEBUG_CLIPBOARD
749 : g_print(" get unicode decoder error\n");
750 : #endif
751 0 : outUnicodeLen = 0;
752 0 : return;
753 : }
754 0 : auto decoder = encoding->NewDecoder();
755 0 : CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(dataLength);
756 0 : if (!needed.isValid() || needed.value() > INT32_MAX) {
757 0 : outUnicodeLen = 0;
758 0 : return;
759 : }
760 :
761 0 : outUnicodeLen = 0;
762 0 : if (needed.value()) {
763 0 : *unicodeData = reinterpret_cast<char16_t*>(
764 0 : moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
765 0 : if (*unicodeData) {
766 : uint32_t result;
767 : size_t read;
768 : size_t written;
769 : bool hadErrors;
770 0 : Tie(result, read, written, hadErrors) =
771 0 : decoder->DecodeToUTF16(AsBytes(MakeSpan(data, dataLength)),
772 : MakeSpan(*unicodeData, needed.value()),
773 0 : true);
774 0 : MOZ_ASSERT(result == kInputEmpty);
775 0 : MOZ_ASSERT(read == size_t(dataLength));
776 0 : MOZ_ASSERT(written <= needed.value());
777 : Unused << hadErrors;
778 : #ifdef DEBUG_CLIPBOARD
779 : if (read != dataLength)
780 : printf("didn't consume all the bytes\n");
781 : #endif
782 0 : outUnicodeLen = written;
783 : // null terminate.
784 0 : (*unicodeData)[outUnicodeLen] = '\0';
785 : }
786 : } // if valid length
787 : }
788 : }
789 :
790 : /*
791 : * get "charset" information from clipboard data
792 : * return value can be:
793 : * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
794 : * 2. "UNKNOWN": mozilla can't detect what encode it use
795 : * 3. other: "text/html" with other charset than utf-16
796 : */
797 0 : void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str)
798 : {
799 : // if detect "FFFE" or "FEFF", assume UTF-16
800 0 : char16_t* beginChar = (char16_t*)data;
801 0 : if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
802 0 : str.AssignLiteral("UTF-16");
803 0 : return;
804 : }
805 : // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
806 0 : const nsDependentCString htmlStr((const char *)data, dataLength);
807 0 : nsACString::const_iterator start, end;
808 0 : htmlStr.BeginReading(start);
809 0 : htmlStr.EndReading(end);
810 0 : nsACString::const_iterator valueStart(start), valueEnd(start);
811 :
812 0 : if (CaseInsensitiveFindInReadable(
813 0 : NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
814 0 : start, end)) {
815 0 : start = end;
816 0 : htmlStr.EndReading(end);
817 :
818 0 : if (CaseInsensitiveFindInReadable(
819 0 : NS_LITERAL_CSTRING("charset="),
820 0 : start, end)) {
821 0 : valueStart = end;
822 0 : start = end;
823 0 : htmlStr.EndReading(end);
824 :
825 0 : if (FindCharInReadable('"', start, end))
826 0 : valueEnd = start;
827 : }
828 : }
829 : // find "charset" in HTML
830 0 : if (valueStart != valueEnd) {
831 0 : str = Substring(valueStart, valueEnd);
832 0 : ToUpperCase(str);
833 : #ifdef DEBUG_CLIPBOARD
834 : printf("Charset of HTML = %s\n", charsetUpperStr.get());
835 : #endif
836 0 : return;
837 : }
838 0 : str.AssignLiteral("UNKNOWN");
839 : }
840 :
841 : static void
842 0 : DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
843 : {
844 : GdkEvent event;
845 0 : event.selection.type = GDK_SELECTION_NOTIFY;
846 0 : event.selection.window = gtk_widget_get_window(widget);
847 0 : event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
848 0 : event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
849 0 : event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
850 0 : event.selection.time = xevent->xselection.time;
851 :
852 0 : gtk_widget_event(widget, &event);
853 0 : }
854 :
855 : static void
856 0 : DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
857 : {
858 0 : GdkWindow *window = gtk_widget_get_window(widget);
859 0 : if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
860 : GdkEvent event;
861 0 : event.property.type = GDK_PROPERTY_NOTIFY;
862 0 : event.property.window = window;
863 0 : event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
864 0 : event.property.time = xevent->xproperty.time;
865 0 : event.property.state = xevent->xproperty.state;
866 :
867 0 : gtk_widget_event(widget, &event);
868 : }
869 0 : }
870 :
871 : struct checkEventContext
872 : {
873 : GtkWidget *cbWidget;
874 : Atom selAtom;
875 : };
876 :
877 : static Bool
878 0 : checkEventProc(Display *display, XEvent *event, XPointer arg)
879 : {
880 0 : checkEventContext *context = (checkEventContext *) arg;
881 :
882 0 : if (event->xany.type == SelectionNotify ||
883 0 : (event->xany.type == PropertyNotify &&
884 0 : event->xproperty.atom == context->selAtom)) {
885 :
886 : GdkWindow *cbWindow =
887 0 : gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
888 0 : event->xany.window);
889 0 : if (cbWindow) {
890 0 : GtkWidget *cbWidget = nullptr;
891 0 : gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
892 0 : if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
893 0 : context->cbWidget = cbWidget;
894 0 : return True;
895 : }
896 : }
897 : }
898 :
899 0 : return False;
900 : }
901 :
902 : // Idle timeout for receiving selection and property notify events (microsec)
903 : static const int kClipboardTimeout = 500000;
904 :
905 0 : static gchar* CopyRetrievedData(const gchar *aData)
906 : {
907 0 : return g_strdup(aData);
908 : }
909 :
910 0 : static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
911 : {
912 : // A negative length indicates that retrieving the data failed.
913 0 : return gtk_selection_data_get_length(aData) >= 0 ?
914 0 : gtk_selection_data_copy(aData) : nullptr;
915 : }
916 :
917 : class RetrievalContext {
918 0 : ~RetrievalContext()
919 0 : {
920 0 : MOZ_ASSERT(!mData, "Wait() wasn't called");
921 0 : }
922 :
923 : public:
924 0 : NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
925 : enum State { INITIAL, COMPLETED, TIMED_OUT };
926 :
927 0 : RetrievalContext() : mState(INITIAL), mData(nullptr) {}
928 :
929 : /**
930 : * Call this when data has been retrieved.
931 : */
932 0 : template <class T> void Complete(T *aData)
933 : {
934 0 : if (mState == INITIAL) {
935 0 : mState = COMPLETED;
936 0 : mData = CopyRetrievedData(aData);
937 : } else {
938 : // Already timed out
939 0 : MOZ_ASSERT(mState == TIMED_OUT);
940 : }
941 0 : }
942 :
943 : /**
944 : * Spins X event loop until timing out or being completed. Returns
945 : * null if we time out, otherwise returns the completed data (passing
946 : * ownership to caller).
947 : */
948 : void *Wait();
949 :
950 : protected:
951 : State mState;
952 : void* mData;
953 : };
954 :
955 : void *
956 0 : RetrievalContext::Wait()
957 : {
958 0 : if (mState == COMPLETED) { // the request completed synchronously
959 0 : void *data = mData;
960 0 : mData = nullptr;
961 0 : return data;
962 : }
963 :
964 0 : GdkDisplay *gdkDisplay = gdk_display_get_default();
965 0 : if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
966 0 : Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
967 : checkEventContext context;
968 0 : context.cbWidget = nullptr;
969 0 : context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
970 : FALSE));
971 :
972 : // Send X events which are relevant to the ongoing selection retrieval
973 : // to the clipboard widget. Wait until either the operation completes, or
974 : // we hit our timeout. All other X events remain queued.
975 :
976 : int select_result;
977 :
978 0 : int cnumber = ConnectionNumber(xDisplay);
979 : fd_set select_set;
980 0 : FD_ZERO(&select_set);
981 0 : FD_SET(cnumber, &select_set);
982 0 : ++cnumber;
983 0 : TimeStamp start = TimeStamp::Now();
984 :
985 0 : do {
986 : XEvent xevent;
987 :
988 0 : while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
989 : (XPointer) &context)) {
990 :
991 0 : if (xevent.xany.type == SelectionNotify)
992 0 : DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
993 : else
994 0 : DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
995 :
996 0 : if (mState == COMPLETED) {
997 0 : void *data = mData;
998 0 : mData = nullptr;
999 0 : return data;
1000 : }
1001 : }
1002 :
1003 0 : TimeStamp now = TimeStamp::Now();
1004 : struct timeval tv;
1005 0 : tv.tv_sec = 0;
1006 0 : tv.tv_usec = std::max<int32_t>(0,
1007 0 : kClipboardTimeout - (now - start).ToMicroseconds());
1008 0 : select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
1009 0 : } while (select_result == 1 ||
1010 0 : (select_result == -1 && errno == EINTR));
1011 : }
1012 : #ifdef DEBUG_CLIPBOARD
1013 : printf("exceeded clipboard timeout\n");
1014 : #endif
1015 0 : mState = TIMED_OUT;
1016 0 : return nullptr;
1017 : }
1018 :
1019 : static void
1020 0 : clipboard_contents_received(GtkClipboard *clipboard,
1021 : GtkSelectionData *selection_data,
1022 : gpointer data)
1023 : {
1024 0 : RetrievalContext *context = static_cast<RetrievalContext*>(data);
1025 0 : context->Complete(selection_data);
1026 0 : context->Release();
1027 0 : }
1028 :
1029 : static GtkSelectionData *
1030 0 : wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
1031 : {
1032 0 : RefPtr<RetrievalContext> context = new RetrievalContext();
1033 : // Balanced by Release in clipboard_contents_received
1034 0 : context.get()->AddRef();
1035 : gtk_clipboard_request_contents(clipboard, target,
1036 : clipboard_contents_received,
1037 0 : context.get());
1038 0 : return static_cast<GtkSelectionData*>(context->Wait());
1039 : }
1040 :
1041 : static void
1042 0 : clipboard_text_received(GtkClipboard *clipboard,
1043 : const gchar *text,
1044 : gpointer data)
1045 : {
1046 0 : RetrievalContext *context = static_cast<RetrievalContext*>(data);
1047 0 : context->Complete(text);
1048 0 : context->Release();
1049 0 : }
1050 :
1051 : static gchar *
1052 0 : wait_for_text(GtkClipboard *clipboard)
1053 : {
1054 0 : RefPtr<RetrievalContext> context = new RetrievalContext();
1055 : // Balanced by Release in clipboard_text_received
1056 0 : context.get()->AddRef();
1057 0 : gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
1058 0 : return static_cast<gchar*>(context->Wait());
1059 : }
1060 :
1061 : static GdkFilterReturn
1062 0 : selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
1063 : {
1064 0 : XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
1065 0 : if (xevent->xany.type == SelectionRequest) {
1066 0 : if (xevent->xselectionrequest.requestor == X11None)
1067 0 : return GDK_FILTER_REMOVE;
1068 :
1069 0 : GdkDisplay *display = gdk_x11_lookup_xdisplay(
1070 0 : xevent->xselectionrequest.display);
1071 0 : if (!display)
1072 0 : return GDK_FILTER_REMOVE;
1073 :
1074 0 : GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
1075 0 : xevent->xselectionrequest.requestor);
1076 0 : if (!window)
1077 0 : return GDK_FILTER_REMOVE;
1078 :
1079 0 : g_object_unref(window);
1080 : }
1081 0 : return GDK_FILTER_CONTINUE;
1082 : }
|