1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
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"
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"
29 // For manipulation of the X event queue
33 #include <sys/types.h>
37 using namespace mozilla
;
39 // Callback when someone asks us for the data
41 clipboard_get_cb(GtkClipboard
*aGtkClipboard
,
42 GtkSelectionData
*aSelectionData
,
46 // Callback when someone asks us to clear a clipboard
48 clipboard_clear_cb(GtkClipboard
*aGtkClipboard
,
52 ConvertHTMLtoUCS2 (guchar
*data
,
54 PRUnichar
**unicodeData
,
55 int32_t &outUnicodeLen
);
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
);
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
)
91 nsClipboard::Init(void)
93 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
95 return NS_ERROR_FAILURE
;
97 os
->AddObserver(this, "quit-application", false);
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
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
);
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())) {
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;
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
);
168 nsXPIDLCString flavorStr
;
169 flavor
->ToString(getter_Copies(flavorStr
));
171 // special case text/unicode since we can handle all of
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);
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
188 // accept any writable image type
189 gtk_target_list_add_image_targets(list
, 0, TRUE
);
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
));
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
;
219 mGlobalOwner
= aOwner
;
220 mGlobalTransferable
= aTransferable
;
221 gtk_clipboard_set_can_store(gtkClipboard
, gtkTargets
, numTargets
);
227 rv
= NS_ERROR_FAILURE
;
230 gtk_target_table_free(gtkTargets
, numTargets
);
231 gtk_target_list_unref(list
);
237 nsClipboard::GetData(nsITransferable
*aTransferable
, int32_t aWhichClipboard
)
240 return NS_ERROR_FAILURE
;
242 GtkClipboard
*clipboard
;
243 clipboard
= gtk_clipboard_get(GetSelectionAtom(aWhichClipboard
));
245 guchar
*data
= nullptr;
247 bool foundData
= false;
248 nsAutoCString foundFlavor
;
250 // Get a list of flavors this transferable can import
251 nsCOMPtr
<nsISupportsArray
> flavors
;
253 rv
= aTransferable
->FlavorsTransferableCanImport(getter_AddRefs(flavors
));
254 if (!flavors
|| NS_FAILED(rv
))
255 return NS_ERROR_FAILURE
;
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
);
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
);
275 // Convert utf-8 into our unicode format.
276 NS_ConvertUTF8toUTF16
ucs2string(new_text
);
277 data
= (guchar
*)ToNewUnicode(ucs2string
);
278 length
= ucs2string
.Length() * 2;
281 foundFlavor
= kUnicodeMime
;
284 // If the type was text/unicode and we couldn't get
285 // text off the clipboard, run the next loop
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
);
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
),
312 aTransferable
->SetTransferData(flavorStr
, byteStream
, sizeof(nsIInputStream
*));
313 gtk_selection_data_free(selectionData
);
317 // Get the atom for this type and try to request it off
319 GdkAtom atom
= gdk_atom_intern(flavorStr
, FALSE
);
320 GtkSelectionData
*selectionData
;
321 selectionData
= wait_for_contents(clipboard
, atom
);
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?
335 data
= (guchar
*)htmlBody
;
336 length
= htmlBodyLen
* 2;
338 data
= (guchar
*)nsMemory::Alloc(length
);
341 memcpy(data
, clipboardData
, length
);
343 gtk_selection_data_free(selectionData
);
345 foundFlavor
= flavorStr
;
352 nsCOMPtr
<nsISupports
> wrapper
;
353 nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor
.get(),
355 getter_AddRefs(wrapper
));
356 aTransferable
->SetTransferData(foundFlavor
.get(),
361 nsMemory::Free(data
);
367 nsClipboard::EmptyClipboard(int32_t aWhichClipboard
)
369 if (aWhichClipboard
== kSelectionClipboard
) {
370 if (mSelectionOwner
) {
371 mSelectionOwner
->LosingOwnership(mSelectionTransferable
);
372 mSelectionOwner
= nullptr;
374 mSelectionTransferable
= nullptr;
378 mGlobalOwner
->LosingOwnership(mGlobalTransferable
);
379 mGlobalOwner
= nullptr;
381 mGlobalTransferable
= nullptr;
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
;
396 GtkSelectionData
*selection_data
=
397 GetTargets(GetSelectionAtom(aWhichClipboard
));
402 GdkAtom
*targets
= nullptr;
404 if (!gtk_selection_data_get_targets(selection_data
,
405 &targets
, &n_targets
) ||
409 // Walk through the provided types and try to match it to a
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
)) {
419 for (int32_t j
= 0; j
< n_targets
; j
++) {
420 gchar
*atom_name
= gdk_atom_name(targets
[j
]);
424 if (!strcmp(atom_name
, aFlavorList
[i
]))
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
))
438 gtk_selection_data_free(selection_data
);
445 nsClipboard::SupportsSelectionClipboard(bool *_retval
)
447 *_retval
= true; // yeah, unix supports the selection clipboard
453 nsClipboard::GetSelectionAtom(int32_t aWhichClipboard
)
455 if (aWhichClipboard
== kGlobalClipboard
)
456 return GDK_SELECTION_CLIPBOARD
;
458 return GDK_SELECTION_PRIMARY
;
463 nsClipboard::GetTargets(GdkAtom aWhichClipboard
)
465 GtkClipboard
*clipboard
= gtk_clipboard_get(aWhichClipboard
);
466 return wait_for_contents(clipboard
, gdk_atom_intern("TARGETS", FALSE
));
470 nsClipboard::GetTransferable(int32_t aWhichClipboard
)
472 nsITransferable
*retval
;
474 if (aWhichClipboard
== kSelectionClipboard
)
475 retval
= mSelectionTransferable
.get();
477 retval
= mGlobalTransferable
.get();
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
491 int32_t whichClipboard
;
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
;
500 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
502 nsCOMPtr
<nsITransferable
> trans
= GetTransferable(whichClipboard
);
504 // We have nothing to serve
505 #ifdef DEBUG_CLIPBOARD
506 printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
507 whichClipboard
== kSelectionClipboard
? "Selection" : "Global");
513 nsCOMPtr
<nsISupports
> item
;
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
),
530 if (!item
|| NS_FAILED(rv
))
533 nsCOMPtr
<nsISupportsString
> wideString
;
534 wideString
= do_QueryInterface(item
);
538 nsAutoString ucs2string
;
539 wideString
->GetData(ucs2string
);
540 char *utf8string
= ToNewUTF8String(ucs2string
);
544 gtk_selection_data_set_text (aSelectionData
, utf8string
,
547 nsMemory::Free(utf8string
);
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
;
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
);
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!?
572 GdkPixbuf
* pixbuf
= nsImageToPixbuf::ImageToPixbuf(image
);
576 gtk_selection_data_set_pixbuf(aSelectionData
, pixbuf
);
577 g_object_unref(pixbuf
);
581 // Try to match up the selection data target to something our
582 // transferable provides.
583 gchar
*target_name
= gdk_atom_name(selectionTarget
);
587 rv
= trans
->GetTransferData(target_name
, getter_AddRefs(item
), &len
);
589 if (!item
|| NS_FAILED(rv
)) {
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
));
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
);
631 nsClipboard::SelectionClearEvent(GtkClipboard
*aGtkClipboard
)
633 int32_t whichClipboard
;
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
;
641 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
643 EmptyClipboard(whichClipboard
);
647 clipboard_get_cb(GtkClipboard
*aGtkClipboard
,
648 GtkSelectionData
*aSelectionData
,
652 nsClipboard
*aClipboard
= static_cast<nsClipboard
*>(user_data
);
653 aClipboard
->SelectionGetEvent(aGtkClipboard
, aSelectionData
);
657 clipboard_clear_cb(GtkClipboard
*aGtkClipboard
,
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')) *
694 memcpy(*unicodeData
, data
+ sizeof(PRUnichar
),
695 outUnicodeLen
* sizeof(PRUnichar
));
696 (*unicodeData
)[outUnicodeLen
] = '\0';
698 } else if (charset
.EqualsLiteral("UNKNOWN")) {
702 // app which use "text/html" to copy&paste
703 nsCOMPtr
<nsIUnicodeDecoder
> decoder
;
706 nsCOMPtr
<nsICharsetConverterManager
> ccm
=
707 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID
, &rv
);
709 #ifdef DEBUG_CLIPBOARD
710 g_print(" can't get CHARSET CONVERTER MANAGER service\n");
715 rv
= ccm
->GetUnicodeDecoder(charset
.get(), getter_AddRefs(decoder
));
717 #ifdef DEBUG_CLIPBOARD
718 g_print(" get unicode decoder error\n");
724 decoder
->GetMaxLength((const char *)data
, dataLength
, &outUnicodeLen
);
725 // |outUnicodeLen| is number of chars
727 *unicodeData
= reinterpret_cast<PRUnichar
*>
728 (nsMemory::Alloc((outUnicodeLen
+ sizeof('\0')) *
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");
738 // null terminate. Convert() doesn't do it for us
739 (*unicodeData
)[outUnicodeLen
] = '\0';
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");
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;"),
771 htmlStr
.EndReading(end
);
773 if (CaseInsensitiveFindInReadable(
774 NS_LITERAL_CSTRING("charset="),
778 htmlStr
.EndReading(end
);
780 if (FindCharInReadable('"', start
, end
))
784 // find "charset" in HTML
785 if (valueStart
!= valueEnd
) {
786 str
= Substring(valueStart
, valueEnd
);
788 #ifdef DEBUG_CLIPBOARD
789 printf("Charset of HTML = %s\n", charsetUpperStr
.get());
793 str
.AssignLiteral("UNKNOWN");
797 DispatchSelectionNotifyEvent(GtkWidget
*widget
, XEvent
*xevent
)
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
);
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
) {
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
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
),
845 GtkWidget
*cbWidget
= nullptr;
846 gdk_window_get_user_data(cbWindow
, (gpointer
*)&cbWidget
);
847 if (cbWidget
&& GTK_IS_WIDGET(cbWidget
)) {
848 context
->cbWidget
= cbWidget
;
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
> {
874 enum State
{ INITIAL
, COMPLETED
, TIMED_OUT
};
876 RetrievalContext() : mState(INITIAL
), mData(nullptr) {}
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
) {
889 mData
= CopyRetrievedData(aData
);
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).
909 RetrievalContext::Wait()
911 if (mState
== COMPLETED
) { // the request completed synchronously
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",
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.
929 int cnumber
= ConnectionNumber(xDisplay
);
931 FD_ZERO(&select_set
);
932 FD_SET(cnumber
, &select_set
);
934 TimeStamp start
= TimeStamp::Now();
939 while (XCheckIfEvent(xDisplay
, &xevent
, checkEventProc
,
940 (XPointer
) &context
)) {
942 if (xevent
.xany
.type
== SelectionNotify
)
943 DispatchSelectionNotifyEvent(context
.cbWidget
, &xevent
);
945 DispatchPropertyNotifyEvent(context
.cbWidget
, &xevent
);
947 if (mState
== COMPLETED
) {
954 TimeStamp now
= TimeStamp::Now();
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");
971 clipboard_contents_received(GtkClipboard
*clipboard
,
972 GtkSelectionData
*selection_data
,
975 RetrievalContext
*context
= static_cast<RetrievalContext
*>(data
);
976 context
->Complete(selection_data
);
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
986 gtk_clipboard_request_contents(clipboard
, target
,
987 clipboard_contents_received
,
989 return static_cast<GtkSelectionData
*>(context
->Wait());
993 clipboard_text_received(GtkClipboard
*clipboard
,
997 RetrievalContext
*context
= static_cast<RetrievalContext
*>(data
);
998 context
->Complete(text
);
1003 wait_for_text(GtkClipboard
*clipboard
)
1005 RefPtr
<RetrievalContext
> context
= new RetrievalContext();
1006 // Balanced by Release in clipboard_text_received
1008 gtk_clipboard_request_text(clipboard
, clipboard_text_received
, context
.get());
1009 return static_cast<gchar
*>(context
->Wait());