Bug 932076 - Add check for MediaExtractor creation failure. r=doublec
[gecko.git] / widget / gtk / nsClipboard.cpp
blob92b76fd30c95a7b6fe5401213a2ed3b3cd3c2106
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/. */
8 #include "mozilla/Util.h"
10 #include "nsClipboard.h"
11 #include "nsSupportsPrimitives.h"
12 #include "nsString.h"
13 #include "nsReadableUtils.h"
14 #include "nsXPIDLString.h"
15 #include "nsPrimitiveHelpers.h"
16 #include "nsICharsetConverterManager.h"
17 #include "nsIServiceManager.h"
18 #include "nsImageToPixbuf.h"
19 #include "nsStringStream.h"
20 #include "nsIObserverService.h"
21 #include "mozilla/Services.h"
22 #include "mozilla/RefPtr.h"
23 #include "mozilla/TimeStamp.h"
25 #include "imgIContainer.h"
27 #include <gtk/gtk.h>
29 // For manipulation of the X event queue
30 #include <X11/Xlib.h>
31 #include <gdk/gdkx.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <errno.h>
35 #include <unistd.h>
37 using namespace mozilla;
39 // Callback when someone asks us for the data
40 void
41 clipboard_get_cb(GtkClipboard *aGtkClipboard,
42 GtkSelectionData *aSelectionData,
43 guint info,
44 gpointer user_data);
46 // Callback when someone asks us to clear a clipboard
47 void
48 clipboard_clear_cb(GtkClipboard *aGtkClipboard,
49 gpointer user_data);
51 static void
52 ConvertHTMLtoUCS2 (guchar *data,
53 int32_t dataLength,
54 PRUnichar **unicodeData,
55 int32_t &outUnicodeLen);
57 static void
58 GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str);
61 // Our own versions of gtk_clipboard_wait_for_contents and
62 // gtk_clipboard_wait_for_text, which don't run the event loop while
63 // waiting for the data. This prevents a lot of problems related to
64 // dispatching events at unexpected times.
66 static GtkSelectionData *
67 wait_for_contents (GtkClipboard *clipboard, GdkAtom target);
69 static gchar *
70 wait_for_text (GtkClipboard *clipboard);
72 nsClipboard::nsClipboard()
76 nsClipboard::~nsClipboard()
78 // We have to clear clipboard before gdk_display_close() call.
79 // See bug 531580 for details.
80 if (mGlobalTransferable) {
81 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
83 if (mSelectionTransferable) {
84 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
88 NS_IMPL_ISUPPORTS1(nsClipboard, nsIClipboard)
90 nsresult
91 nsClipboard::Init(void)
93 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
94 if (!os)
95 return NS_ERROR_FAILURE;
97 os->AddObserver(this, "quit-application", false);
99 return NS_OK;
102 NS_IMETHODIMP
103 nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
105 if (strcmp(aTopic, "quit-application") == 0) {
106 // application is going to quit, save clipboard content
107 Store();
109 return NS_OK;
112 nsresult
113 nsClipboard::Store(void)
115 // Ask the clipboard manager to store the current clipboard content
116 if (mGlobalTransferable) {
117 GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
118 gtk_clipboard_store(clipboard);
120 return NS_OK;
123 NS_IMETHODIMP
124 nsClipboard::SetData(nsITransferable *aTransferable,
125 nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
127 // See if we can short cut
128 if ((aWhichClipboard == kGlobalClipboard &&
129 aTransferable == mGlobalTransferable.get() &&
130 aOwner == mGlobalOwner.get()) ||
131 (aWhichClipboard == kSelectionClipboard &&
132 aTransferable == mSelectionTransferable.get() &&
133 aOwner == mSelectionOwner.get())) {
134 return NS_OK;
137 nsresult rv;
138 if (!mPrivacyHandler) {
139 rv = NS_NewClipboardPrivacyHandler(getter_AddRefs(mPrivacyHandler));
140 NS_ENSURE_SUCCESS(rv, rv);
142 rv = mPrivacyHandler->PrepareDataForClipboard(aTransferable);
143 NS_ENSURE_SUCCESS(rv, rv);
145 // Clear out the clipboard in order to set the new data
146 EmptyClipboard(aWhichClipboard);
148 // List of suported targets
149 GtkTargetList *list = gtk_target_list_new(nullptr, 0);
151 // Get the types of supported flavors
152 nsCOMPtr<nsISupportsArray> flavors;
154 rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
155 if (!flavors || NS_FAILED(rv))
156 return NS_ERROR_FAILURE;
158 // Add all the flavors to this widget's supported type.
159 bool imagesAdded = false;
160 uint32_t count;
161 flavors->Count(&count);
162 for (uint32_t i=0; i < count; i++) {
163 nsCOMPtr<nsISupports> tastesLike;
164 flavors->GetElementAt(i, getter_AddRefs(tastesLike));
165 nsCOMPtr<nsISupportsCString> flavor = do_QueryInterface(tastesLike);
167 if (flavor) {
168 nsXPIDLCString flavorStr;
169 flavor->ToString(getter_Copies(flavorStr));
171 // special case text/unicode since we can handle all of
172 // the string types
173 if (!strcmp(flavorStr, kUnicodeMime)) {
174 gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
175 gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
176 gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
177 gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
178 continue;
181 if (flavorStr.EqualsLiteral(kNativeImageMime) ||
182 flavorStr.EqualsLiteral(kPNGImageMime) ||
183 flavorStr.EqualsLiteral(kJPEGImageMime) ||
184 flavorStr.EqualsLiteral(kJPGImageMime) ||
185 flavorStr.EqualsLiteral(kGIFImageMime)) {
186 // don't bother adding image targets twice
187 if (!imagesAdded) {
188 // accept any writable image type
189 gtk_target_list_add_image_targets(list, 0, TRUE);
190 imagesAdded = true;
192 continue;
195 // Add this to our list of valid targets
196 GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
197 gtk_target_list_add(list, atom, 0, 0);
201 // Get GTK clipboard (CLIPBOARD or PRIMARY)
202 GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
204 gint numTargets;
205 GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
207 // Set getcallback and request to store data after an application exit
208 if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
209 clipboard_get_cb, clipboard_clear_cb, this))
211 // We managed to set-up the clipboard so update internal state
212 // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()
213 // which reset our internal state
214 if (aWhichClipboard == kSelectionClipboard) {
215 mSelectionOwner = aOwner;
216 mSelectionTransferable = aTransferable;
218 else {
219 mGlobalOwner = aOwner;
220 mGlobalTransferable = aTransferable;
221 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
224 rv = NS_OK;
226 else {
227 rv = NS_ERROR_FAILURE;
230 gtk_target_table_free(gtkTargets, numTargets);
231 gtk_target_list_unref(list);
233 return rv;
236 NS_IMETHODIMP
237 nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
239 if (!aTransferable)
240 return NS_ERROR_FAILURE;
242 GtkClipboard *clipboard;
243 clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
245 guchar *data = nullptr;
246 gint length = 0;
247 bool foundData = false;
248 nsAutoCString foundFlavor;
250 // Get a list of flavors this transferable can import
251 nsCOMPtr<nsISupportsArray> flavors;
252 nsresult rv;
253 rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
254 if (!flavors || NS_FAILED(rv))
255 return NS_ERROR_FAILURE;
257 uint32_t count;
258 flavors->Count(&count);
259 for (uint32_t i=0; i < count; i++) {
260 nsCOMPtr<nsISupports> genericFlavor;
261 flavors->GetElementAt(i, getter_AddRefs(genericFlavor));
263 nsCOMPtr<nsISupportsCString> currentFlavor;
264 currentFlavor = do_QueryInterface(genericFlavor);
266 if (currentFlavor) {
267 nsXPIDLCString flavorStr;
268 currentFlavor->ToString(getter_Copies(flavorStr));
270 // Special case text/unicode since we can convert any
271 // string into text/unicode
272 if (!strcmp(flavorStr, kUnicodeMime)) {
273 gchar* new_text = wait_for_text(clipboard);
274 if (new_text) {
275 // Convert utf-8 into our unicode format.
276 NS_ConvertUTF8toUTF16 ucs2string(new_text);
277 data = (guchar *)ToNewUnicode(ucs2string);
278 length = ucs2string.Length() * 2;
279 g_free(new_text);
280 foundData = true;
281 foundFlavor = kUnicodeMime;
282 break;
284 // If the type was text/unicode and we couldn't get
285 // text off the clipboard, run the next loop
286 // iteration.
287 continue;
290 // For images, we must wrap the data in an nsIInputStream then return instead of break,
291 // because that code below won't help us.
292 if (!strcmp(flavorStr, kJPEGImageMime) ||
293 !strcmp(flavorStr, kJPGImageMime) ||
294 !strcmp(flavorStr, kPNGImageMime) ||
295 !strcmp(flavorStr, kGIFImageMime)) {
296 // Emulate support for image/jpg
297 if (!strcmp(flavorStr, kJPGImageMime)) {
298 flavorStr.Assign(kJPEGImageMime);
301 GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
303 GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
304 if (!selectionData)
305 continue;
307 nsCOMPtr<nsIInputStream> byteStream;
308 NS_NewByteInputStream(getter_AddRefs(byteStream),
309 (const char*)gtk_selection_data_get_data(selectionData),
310 gtk_selection_data_get_length(selectionData),
311 NS_ASSIGNMENT_COPY);
312 aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
313 gtk_selection_data_free(selectionData);
314 return NS_OK;
317 // Get the atom for this type and try to request it off
318 // the clipboard.
319 GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
320 GtkSelectionData *selectionData;
321 selectionData = wait_for_contents(clipboard, atom);
322 if (selectionData) {
323 const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
324 length = gtk_selection_data_get_length(selectionData);
325 // Special case text/html since we can convert into UCS2
326 if (!strcmp(flavorStr, kHTMLMime)) {
327 PRUnichar* htmlBody= nullptr;
328 int32_t htmlBodyLen = 0;
329 // Convert text/html into our unicode format
330 ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
331 &htmlBody, htmlBodyLen);
332 // Try next data format?
333 if (!htmlBodyLen)
334 continue;
335 data = (guchar *)htmlBody;
336 length = htmlBodyLen * 2;
337 } else {
338 data = (guchar *)nsMemory::Alloc(length);
339 if (!data)
340 break;
341 memcpy(data, clipboardData, length);
343 gtk_selection_data_free(selectionData);
344 foundData = true;
345 foundFlavor = flavorStr;
346 break;
351 if (foundData) {
352 nsCOMPtr<nsISupports> wrapper;
353 nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
354 data, length,
355 getter_AddRefs(wrapper));
356 aTransferable->SetTransferData(foundFlavor.get(),
357 wrapper, length);
360 if (data)
361 nsMemory::Free(data);
363 return NS_OK;
366 NS_IMETHODIMP
367 nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
369 if (aWhichClipboard == kSelectionClipboard) {
370 if (mSelectionOwner) {
371 mSelectionOwner->LosingOwnership(mSelectionTransferable);
372 mSelectionOwner = nullptr;
374 mSelectionTransferable = nullptr;
376 else {
377 if (mGlobalOwner) {
378 mGlobalOwner->LosingOwnership(mGlobalTransferable);
379 mGlobalOwner = nullptr;
381 mGlobalTransferable = nullptr;
384 return NS_OK;
387 NS_IMETHODIMP
388 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
389 int32_t aWhichClipboard, bool *_retval)
391 if (!aFlavorList || !_retval)
392 return NS_ERROR_NULL_POINTER;
394 *_retval = false;
396 GtkSelectionData *selection_data =
397 GetTargets(GetSelectionAtom(aWhichClipboard));
398 if (!selection_data)
399 return NS_OK;
401 gint n_targets = 0;
402 GdkAtom *targets = nullptr;
404 if (!gtk_selection_data_get_targets(selection_data,
405 &targets, &n_targets) ||
406 !n_targets)
407 return NS_OK;
409 // Walk through the provided types and try to match it to a
410 // provided type.
411 for (uint32_t i = 0; i < aLength && !*_retval; i++) {
412 // We special case text/unicode here.
413 if (!strcmp(aFlavorList[i], kUnicodeMime) &&
414 gtk_selection_data_targets_include_text(selection_data)) {
415 *_retval = true;
416 break;
419 for (int32_t j = 0; j < n_targets; j++) {
420 gchar *atom_name = gdk_atom_name(targets[j]);
421 if (!atom_name)
422 continue;
424 if (!strcmp(atom_name, aFlavorList[i]))
425 *_retval = true;
427 // X clipboard supports image/jpeg, but we want to emulate support
428 // for image/jpg as well
429 if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
430 *_retval = true;
432 g_free(atom_name);
434 if (*_retval)
435 break;
438 gtk_selection_data_free(selection_data);
439 g_free(targets);
441 return NS_OK;
444 NS_IMETHODIMP
445 nsClipboard::SupportsSelectionClipboard(bool *_retval)
447 *_retval = true; // yeah, unix supports the selection clipboard
448 return NS_OK;
451 /* static */
452 GdkAtom
453 nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
455 if (aWhichClipboard == kGlobalClipboard)
456 return GDK_SELECTION_CLIPBOARD;
458 return GDK_SELECTION_PRIMARY;
461 /* static */
462 GtkSelectionData *
463 nsClipboard::GetTargets(GdkAtom aWhichClipboard)
465 GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
466 return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
469 nsITransferable *
470 nsClipboard::GetTransferable(int32_t aWhichClipboard)
472 nsITransferable *retval;
474 if (aWhichClipboard == kSelectionClipboard)
475 retval = mSelectionTransferable.get();
476 else
477 retval = mGlobalTransferable.get();
479 return retval;
482 void
483 nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard,
484 GtkSelectionData *aSelectionData)
486 // Someone has asked us to hand them something. The first thing
487 // that we want to do is see if that something includes text. If
488 // it does, try to give it text/unicode after converting it to
489 // utf-8.
491 int32_t whichClipboard;
493 // which clipboard?
494 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
495 if (selection == GDK_SELECTION_PRIMARY)
496 whichClipboard = kSelectionClipboard;
497 else if (selection == GDK_SELECTION_CLIPBOARD)
498 whichClipboard = kGlobalClipboard;
499 else
500 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
502 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
503 if (!trans) {
504 // We have nothing to serve
505 #ifdef DEBUG_CLIPBOARD
506 printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
507 whichClipboard == kSelectionClipboard ? "Selection" : "Global");
508 #endif
509 return;
512 nsresult rv;
513 nsCOMPtr<nsISupports> item;
514 uint32_t len;
517 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
519 // Check to see if the selection data includes any of the string
520 // types that we support.
521 if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
522 selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
523 selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
524 selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
525 // Try to convert our internal type into a text string. Get
526 // the transferable for this clipboard and try to get the
527 // text/unicode type for it.
528 rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
529 &len);
530 if (!item || NS_FAILED(rv))
531 return;
533 nsCOMPtr<nsISupportsString> wideString;
534 wideString = do_QueryInterface(item);
535 if (!wideString)
536 return;
538 nsAutoString ucs2string;
539 wideString->GetData(ucs2string);
540 char *utf8string = ToNewUTF8String(ucs2string);
541 if (!utf8string)
542 return;
544 gtk_selection_data_set_text (aSelectionData, utf8string,
545 strlen(utf8string));
547 nsMemory::Free(utf8string);
548 return;
551 // Check to see if the selection data is an image type
552 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
553 // Look through our transfer data for the image
554 static const char* const imageMimeTypes[] = {
555 kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
556 nsCOMPtr<nsISupports> item;
557 uint32_t len;
558 nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
559 for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
560 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(item), &len);
561 ptrPrimitive = do_QueryInterface(item);
563 if (!ptrPrimitive)
564 return;
566 nsCOMPtr<nsISupports> primitiveData;
567 ptrPrimitive->GetData(getter_AddRefs(primitiveData));
568 nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
569 if (!image) // Not getting an image for an image mime type!?
570 return;
572 GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
573 if (!pixbuf)
574 return;
576 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
577 g_object_unref(pixbuf);
578 return;
581 // Try to match up the selection data target to something our
582 // transferable provides.
583 gchar *target_name = gdk_atom_name(selectionTarget);
584 if (!target_name)
585 return;
587 rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
588 // nothing found?
589 if (!item || NS_FAILED(rv)) {
590 g_free(target_name);
591 return;
594 void *primitive_data = nullptr;
595 nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
596 &primitive_data, len);
598 if (primitive_data) {
599 // Check to see if the selection data is text/html
600 if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
602 * "text/html" can be encoded UCS2. It is recommended that
603 * documents transmitted as UCS2 always begin with a ZERO-WIDTH
604 * NON-BREAKING SPACE character (hexadecimal FEFF, also called
605 * Byte Order Mark (BOM)). Adding BOM can help other app to
606 * detect mozilla use UCS2 encoding when copy-paste.
608 guchar *buffer = (guchar *)
609 nsMemory::Alloc((len * sizeof(guchar)) + sizeof(PRUnichar));
610 if (!buffer)
611 return;
612 PRUnichar prefix = 0xFEFF;
613 memcpy(buffer, &prefix, sizeof(prefix));
614 memcpy(buffer + sizeof(prefix), primitive_data, len);
615 nsMemory::Free((guchar *)primitive_data);
616 primitive_data = (guchar *)buffer;
617 len += sizeof(prefix);
620 gtk_selection_data_set(aSelectionData, selectionTarget,
621 8, /* 8 bits in a unit */
622 (const guchar *)primitive_data, len);
623 nsMemory::Free(primitive_data);
626 g_free(target_name);
630 void
631 nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
633 int32_t whichClipboard;
635 // which clipboard?
636 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
637 whichClipboard = kSelectionClipboard;
638 else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
639 whichClipboard = kGlobalClipboard;
640 else
641 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
643 EmptyClipboard(whichClipboard);
646 void
647 clipboard_get_cb(GtkClipboard *aGtkClipboard,
648 GtkSelectionData *aSelectionData,
649 guint info,
650 gpointer user_data)
652 nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
653 aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
656 void
657 clipboard_clear_cb(GtkClipboard *aGtkClipboard,
658 gpointer user_data)
660 nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
661 aClipboard->SelectionClearEvent(aGtkClipboard);
665 * when copy-paste, mozilla wants data encoded using UCS2,
666 * other app such as StarOffice use "text/html"(RFC2854).
667 * This function convert data(got from GTK clipboard)
668 * to data mozilla wanted.
670 * data from GTK clipboard can be 3 forms:
671 * 1. From current mozilla
672 * "text/html", charset = utf-16
673 * 2. From old version mozilla or mozilla-based app
674 * content("body" only), charset = utf-16
675 * 3. From other app who use "text/html" when copy-paste
676 * "text/html", has "charset" info
678 * data : got from GTK clipboard
679 * dataLength: got from GTK clipboard
680 * body : pass to Mozilla
681 * bodyLength: pass to Mozilla
683 void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
684 PRUnichar** unicodeData, int32_t& outUnicodeLen)
686 nsAutoCString charset;
687 GetHTMLCharset(data, dataLength, charset);// get charset of HTML
688 if (charset.EqualsLiteral("UTF-16")) {//current mozilla
689 outUnicodeLen = (dataLength / 2) - 1;
690 *unicodeData = reinterpret_cast<PRUnichar*>
691 (nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
692 sizeof(PRUnichar)));
693 if (*unicodeData) {
694 memcpy(*unicodeData, data + sizeof(PRUnichar),
695 outUnicodeLen * sizeof(PRUnichar));
696 (*unicodeData)[outUnicodeLen] = '\0';
698 } else if (charset.EqualsLiteral("UNKNOWN")) {
699 outUnicodeLen = 0;
700 return;
701 } else {
702 // app which use "text/html" to copy&paste
703 nsCOMPtr<nsIUnicodeDecoder> decoder;
704 nsresult rv;
705 // get the decoder
706 nsCOMPtr<nsICharsetConverterManager> ccm =
707 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
708 if (NS_FAILED(rv)) {
709 #ifdef DEBUG_CLIPBOARD
710 g_print(" can't get CHARSET CONVERTER MANAGER service\n");
711 #endif
712 outUnicodeLen = 0;
713 return;
715 rv = ccm->GetUnicodeDecoder(charset.get(), getter_AddRefs(decoder));
716 if (NS_FAILED(rv)) {
717 #ifdef DEBUG_CLIPBOARD
718 g_print(" get unicode decoder error\n");
719 #endif
720 outUnicodeLen = 0;
721 return;
723 // converting
724 decoder->GetMaxLength((const char *)data, dataLength, &outUnicodeLen);
725 // |outUnicodeLen| is number of chars
726 if (outUnicodeLen) {
727 *unicodeData = reinterpret_cast<PRUnichar*>
728 (nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
729 sizeof(PRUnichar)));
730 if (*unicodeData) {
731 int32_t numberTmp = dataLength;
732 decoder->Convert((const char *)data, &numberTmp,
733 *unicodeData, &outUnicodeLen);
734 #ifdef DEBUG_CLIPBOARD
735 if (numberTmp != dataLength)
736 printf("didn't consume all the bytes\n");
737 #endif
738 // null terminate. Convert() doesn't do it for us
739 (*unicodeData)[outUnicodeLen] = '\0';
741 } // if valid length
746 * get "charset" information from clipboard data
747 * return value can be:
748 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
749 * 2. "UNKNOWN": mozilla can't detect what encode it use
750 * 3. other: "text/html" with other charset than utf-16
752 void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str)
754 // if detect "FFFE" or "FEFF", assume UTF-16
755 PRUnichar* beginChar = (PRUnichar*)data;
756 if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
757 str.AssignLiteral("UTF-16");
758 return;
760 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
761 const nsDependentCString htmlStr((const char *)data, dataLength);
762 nsACString::const_iterator start, end;
763 htmlStr.BeginReading(start);
764 htmlStr.EndReading(end);
765 nsACString::const_iterator valueStart(start), valueEnd(start);
767 if (CaseInsensitiveFindInReadable(
768 NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
769 start, end)) {
770 start = end;
771 htmlStr.EndReading(end);
773 if (CaseInsensitiveFindInReadable(
774 NS_LITERAL_CSTRING("charset="),
775 start, end)) {
776 valueStart = end;
777 start = end;
778 htmlStr.EndReading(end);
780 if (FindCharInReadable('"', start, end))
781 valueEnd = start;
784 // find "charset" in HTML
785 if (valueStart != valueEnd) {
786 str = Substring(valueStart, valueEnd);
787 ToUpperCase(str);
788 #ifdef DEBUG_CLIPBOARD
789 printf("Charset of HTML = %s\n", charsetUpperStr.get());
790 #endif
791 return;
793 str.AssignLiteral("UNKNOWN");
796 static void
797 DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
799 GdkEvent event;
800 event.selection.type = GDK_SELECTION_NOTIFY;
801 event.selection.window = gtk_widget_get_window(widget);
802 event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
803 event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
804 event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
805 event.selection.time = xevent->xselection.time;
807 gtk_widget_event(widget, &event);
810 static void
811 DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
813 GdkWindow *window = gtk_widget_get_window(widget);
814 if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
815 GdkEvent event;
816 event.property.type = GDK_PROPERTY_NOTIFY;
817 event.property.window = window;
818 event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
819 event.property.time = xevent->xproperty.time;
820 event.property.state = xevent->xproperty.state;
822 gtk_widget_event(widget, &event);
826 struct checkEventContext
828 GtkWidget *cbWidget;
829 Atom selAtom;
832 static Bool
833 checkEventProc(Display *display, XEvent *event, XPointer arg)
835 checkEventContext *context = (checkEventContext *) arg;
837 if (event->xany.type == SelectionNotify ||
838 (event->xany.type == PropertyNotify &&
839 event->xproperty.atom == context->selAtom)) {
841 GdkWindow *cbWindow =
842 gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
843 event->xany.window);
844 if (cbWindow) {
845 GtkWidget *cbWidget = nullptr;
846 gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
847 if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
848 context->cbWidget = cbWidget;
849 return True;
854 return False;
857 // Idle timeout for receiving selection and property notify events (microsec)
858 static const int kClipboardTimeout = 500000;
860 static gchar* CopyRetrievedData(const gchar *aData)
862 return g_strdup(aData);
865 static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
867 // A negative length indicates that retrieving the data failed.
868 return gtk_selection_data_get_length(aData) >= 0 ?
869 gtk_selection_data_copy(aData) : nullptr;
872 class RetrievalContext : public RefCounted<RetrievalContext> {
873 public:
874 enum State { INITIAL, COMPLETED, TIMED_OUT };
876 RetrievalContext() : mState(INITIAL), mData(nullptr) {}
877 ~RetrievalContext()
879 MOZ_ASSERT(!mData, "Wait() wasn't called");
883 * Call this when data has been retrieved.
885 template <class T> void Complete(T *aData)
887 if (mState == INITIAL) {
888 mState = COMPLETED;
889 mData = CopyRetrievedData(aData);
890 } else {
891 // Already timed out
892 MOZ_ASSERT(mState == TIMED_OUT);
897 * Spins X event loop until timing out or being completed. Returns
898 * null if we time out, otherwise returns the completed data (passing
899 * ownership to caller).
901 void *Wait();
903 protected:
904 State mState;
905 void* mData;
908 void *
909 RetrievalContext::Wait()
911 if (mState == COMPLETED) { // the request completed synchronously
912 void *data = mData;
913 mData = nullptr;
914 return data;
917 Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) ;
918 checkEventContext context;
919 context.cbWidget = nullptr;
920 context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
921 FALSE));
923 // Send X events which are relevant to the ongoing selection retrieval
924 // to the clipboard widget. Wait until either the operation completes, or
925 // we hit our timeout. All other X events remain queued.
927 int select_result;
929 int cnumber = ConnectionNumber(xDisplay);
930 fd_set select_set;
931 FD_ZERO(&select_set);
932 FD_SET(cnumber, &select_set);
933 ++cnumber;
934 TimeStamp start = TimeStamp::Now();
936 do {
937 XEvent xevent;
939 while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
940 (XPointer) &context)) {
942 if (xevent.xany.type == SelectionNotify)
943 DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
944 else
945 DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
947 if (mState == COMPLETED) {
948 void *data = mData;
949 mData = nullptr;
950 return data;
954 TimeStamp now = TimeStamp::Now();
955 struct timeval tv;
956 tv.tv_sec = 0;
957 tv.tv_usec = std::max<int32_t>(0,
958 kClipboardTimeout - (now - start).ToMicroseconds());
959 select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
960 } while (select_result == 1 ||
961 (select_result == -1 && errno == EINTR));
963 #ifdef DEBUG_CLIPBOARD
964 printf("exceeded clipboard timeout\n");
965 #endif
966 mState = TIMED_OUT;
967 return nullptr;
970 static void
971 clipboard_contents_received(GtkClipboard *clipboard,
972 GtkSelectionData *selection_data,
973 gpointer data)
975 RetrievalContext *context = static_cast<RetrievalContext*>(data);
976 context->Complete(selection_data);
977 context->Release();
980 static GtkSelectionData *
981 wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
983 RefPtr<RetrievalContext> context = new RetrievalContext();
984 // Balanced by Release in clipboard_contents_received
985 context->AddRef();
986 gtk_clipboard_request_contents(clipboard, target,
987 clipboard_contents_received,
988 context.get());
989 return static_cast<GtkSelectionData*>(context->Wait());
992 static void
993 clipboard_text_received(GtkClipboard *clipboard,
994 const gchar *text,
995 gpointer data)
997 RetrievalContext *context = static_cast<RetrievalContext*>(data);
998 context->Complete(text);
999 context->Release();
1002 static gchar *
1003 wait_for_text(GtkClipboard *clipboard)
1005 RefPtr<RetrievalContext> context = new RetrievalContext();
1006 // Balanced by Release in clipboard_text_received
1007 context->AddRef();
1008 gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
1009 return static_cast<gchar*>(context->Wait());