widget/gtk/nsClipboard.cpp

branch
TOR_BUG_9701
changeset 10
ac0c01689b40
equal deleted inserted replaced
-1:000000000000 0:f72be9203aff
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 "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"
24
25 #include "imgIContainer.h"
26
27 #include <gtk/gtk.h>
28
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>
36
37 using namespace mozilla;
38
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);
45
46 // Callback when someone asks us to clear a clipboard
47 void
48 clipboard_clear_cb(GtkClipboard *aGtkClipboard,
49 gpointer user_data);
50
51 static void
52 ConvertHTMLtoUCS2 (guchar *data,
53 int32_t dataLength,
54 char16_t **unicodeData,
55 int32_t &outUnicodeLen);
56
57 static void
58 GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str);
59
60
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.
65
66 static GtkSelectionData *
67 wait_for_contents (GtkClipboard *clipboard, GdkAtom target);
68
69 static gchar *
70 wait_for_text (GtkClipboard *clipboard);
71
72 nsClipboard::nsClipboard()
73 {
74 }
75
76 nsClipboard::~nsClipboard()
77 {
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));
82 }
83 if (mSelectionTransferable) {
84 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
85 }
86 }
87
88 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
89
90 nsresult
91 nsClipboard::Init(void)
92 {
93 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
94 if (!os)
95 return NS_ERROR_FAILURE;
96
97 os->AddObserver(this, "quit-application", false);
98
99 return NS_OK;
100 }
101
102 NS_IMETHODIMP
103 nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
104 {
105 if (strcmp(aTopic, "quit-application") == 0) {
106 // application is going to quit, save clipboard content
107 Store();
108 }
109 return NS_OK;
110 }
111
112 nsresult
113 nsClipboard::Store(void)
114 {
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);
119 }
120 return NS_OK;
121 }
122
123 NS_IMETHODIMP
124 nsClipboard::SetData(nsITransferable *aTransferable,
125 nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
126 {
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;
135 }
136
137 nsresult rv;
138 if (!mPrivacyHandler) {
139 rv = NS_NewClipboardPrivacyHandler(getter_AddRefs(mPrivacyHandler));
140 NS_ENSURE_SUCCESS(rv, rv);
141 }
142 rv = mPrivacyHandler->PrepareDataForClipboard(aTransferable);
143 NS_ENSURE_SUCCESS(rv, rv);
144
145 // Clear out the clipboard in order to set the new data
146 EmptyClipboard(aWhichClipboard);
147
148 // List of suported targets
149 GtkTargetList *list = gtk_target_list_new(nullptr, 0);
150
151 // Get the types of supported flavors
152 nsCOMPtr<nsISupportsArray> flavors;
153
154 rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
155 if (!flavors || NS_FAILED(rv))
156 return NS_ERROR_FAILURE;
157
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);
166
167 if (flavor) {
168 nsXPIDLCString flavorStr;
169 flavor->ToString(getter_Copies(flavorStr));
170
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;
179 }
180
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;
191 }
192 continue;
193 }
194
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);
198 }
199 }
200
201 // Get GTK clipboard (CLIPBOARD or PRIMARY)
202 GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
203
204 gint numTargets;
205 GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
206
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))
210 {
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;
217 }
218 else {
219 mGlobalOwner = aOwner;
220 mGlobalTransferable = aTransferable;
221 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
222 }
223
224 rv = NS_OK;
225 }
226 else {
227 rv = NS_ERROR_FAILURE;
228 }
229
230 gtk_target_table_free(gtkTargets, numTargets);
231 gtk_target_list_unref(list);
232
233 return rv;
234 }
235
236 NS_IMETHODIMP
237 nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
238 {
239 if (!aTransferable)
240 return NS_ERROR_FAILURE;
241
242 GtkClipboard *clipboard;
243 clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
244
245 guchar *data = nullptr;
246 gint length = 0;
247 bool foundData = false;
248 nsAutoCString foundFlavor;
249
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;
256
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));
262
263 nsCOMPtr<nsISupportsCString> currentFlavor;
264 currentFlavor = do_QueryInterface(genericFlavor);
265
266 if (currentFlavor) {
267 nsXPIDLCString flavorStr;
268 currentFlavor->ToString(getter_Copies(flavorStr));
269
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;
283 }
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;
288 }
289
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);
299 }
300
301 GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
302
303 GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
304 if (!selectionData)
305 continue;
306
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;
315 }
316
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 char16_t* 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);
342 }
343 gtk_selection_data_free(selectionData);
344 foundData = true;
345 foundFlavor = flavorStr;
346 break;
347 }
348 }
349 }
350
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);
358 }
359
360 if (data)
361 nsMemory::Free(data);
362
363 return NS_OK;
364 }
365
366 NS_IMETHODIMP
367 nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
368 {
369 if (aWhichClipboard == kSelectionClipboard) {
370 if (mSelectionOwner) {
371 mSelectionOwner->LosingOwnership(mSelectionTransferable);
372 mSelectionOwner = nullptr;
373 }
374 mSelectionTransferable = nullptr;
375 }
376 else {
377 if (mGlobalOwner) {
378 mGlobalOwner->LosingOwnership(mGlobalTransferable);
379 mGlobalOwner = nullptr;
380 }
381 mGlobalTransferable = nullptr;
382 }
383
384 return NS_OK;
385 }
386
387 NS_IMETHODIMP
388 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
389 int32_t aWhichClipboard, bool *_retval)
390 {
391 if (!aFlavorList || !_retval)
392 return NS_ERROR_NULL_POINTER;
393
394 *_retval = false;
395
396 GtkSelectionData *selection_data =
397 GetTargets(GetSelectionAtom(aWhichClipboard));
398 if (!selection_data)
399 return NS_OK;
400
401 gint n_targets = 0;
402 GdkAtom *targets = nullptr;
403
404 if (!gtk_selection_data_get_targets(selection_data,
405 &targets, &n_targets) ||
406 !n_targets)
407 return NS_OK;
408
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;
417 }
418
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;
423
424 if (!strcmp(atom_name, aFlavorList[i]))
425 *_retval = true;
426
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;
431
432 g_free(atom_name);
433
434 if (*_retval)
435 break;
436 }
437 }
438 gtk_selection_data_free(selection_data);
439 g_free(targets);
440
441 return NS_OK;
442 }
443
444 NS_IMETHODIMP
445 nsClipboard::SupportsSelectionClipboard(bool *_retval)
446 {
447 *_retval = true; // yeah, unix supports the selection clipboard
448 return NS_OK;
449 }
450
451 NS_IMETHODIMP
452 nsClipboard::SupportsFindClipboard(bool* _retval)
453 {
454 *_retval = false;
455 return NS_OK;
456 }
457
458 /* static */
459 GdkAtom
460 nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
461 {
462 if (aWhichClipboard == kGlobalClipboard)
463 return GDK_SELECTION_CLIPBOARD;
464
465 return GDK_SELECTION_PRIMARY;
466 }
467
468 /* static */
469 GtkSelectionData *
470 nsClipboard::GetTargets(GdkAtom aWhichClipboard)
471 {
472 GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
473 return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
474 }
475
476 nsITransferable *
477 nsClipboard::GetTransferable(int32_t aWhichClipboard)
478 {
479 nsITransferable *retval;
480
481 if (aWhichClipboard == kSelectionClipboard)
482 retval = mSelectionTransferable.get();
483 else
484 retval = mGlobalTransferable.get();
485
486 return retval;
487 }
488
489 void
490 nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard,
491 GtkSelectionData *aSelectionData)
492 {
493 // Someone has asked us to hand them something. The first thing
494 // that we want to do is see if that something includes text. If
495 // it does, try to give it text/unicode after converting it to
496 // utf-8.
497
498 int32_t whichClipboard;
499
500 // which clipboard?
501 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
502 if (selection == GDK_SELECTION_PRIMARY)
503 whichClipboard = kSelectionClipboard;
504 else if (selection == GDK_SELECTION_CLIPBOARD)
505 whichClipboard = kGlobalClipboard;
506 else
507 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
508
509 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
510 if (!trans) {
511 // We have nothing to serve
512 #ifdef DEBUG_CLIPBOARD
513 printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
514 whichClipboard == kSelectionClipboard ? "Selection" : "Global");
515 #endif
516 return;
517 }
518
519 nsresult rv;
520 nsCOMPtr<nsISupports> item;
521 uint32_t len;
522
523
524 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
525
526 // Check to see if the selection data includes any of the string
527 // types that we support.
528 if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
529 selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
530 selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
531 selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
532 // Try to convert our internal type into a text string. Get
533 // the transferable for this clipboard and try to get the
534 // text/unicode type for it.
535 rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
536 &len);
537 if (!item || NS_FAILED(rv))
538 return;
539
540 nsCOMPtr<nsISupportsString> wideString;
541 wideString = do_QueryInterface(item);
542 if (!wideString)
543 return;
544
545 nsAutoString ucs2string;
546 wideString->GetData(ucs2string);
547 char *utf8string = ToNewUTF8String(ucs2string);
548 if (!utf8string)
549 return;
550
551 gtk_selection_data_set_text (aSelectionData, utf8string,
552 strlen(utf8string));
553
554 nsMemory::Free(utf8string);
555 return;
556 }
557
558 // Check to see if the selection data is an image type
559 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
560 // Look through our transfer data for the image
561 static const char* const imageMimeTypes[] = {
562 kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
563 nsCOMPtr<nsISupports> item;
564 uint32_t len;
565 nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
566 for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
567 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(item), &len);
568 ptrPrimitive = do_QueryInterface(item);
569 }
570 if (!ptrPrimitive)
571 return;
572
573 nsCOMPtr<nsISupports> primitiveData;
574 ptrPrimitive->GetData(getter_AddRefs(primitiveData));
575 nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
576 if (!image) // Not getting an image for an image mime type!?
577 return;
578
579 GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
580 if (!pixbuf)
581 return;
582
583 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
584 g_object_unref(pixbuf);
585 return;
586 }
587
588 // Try to match up the selection data target to something our
589 // transferable provides.
590 gchar *target_name = gdk_atom_name(selectionTarget);
591 if (!target_name)
592 return;
593
594 rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
595 // nothing found?
596 if (!item || NS_FAILED(rv)) {
597 g_free(target_name);
598 return;
599 }
600
601 void *primitive_data = nullptr;
602 nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
603 &primitive_data, len);
604
605 if (primitive_data) {
606 // Check to see if the selection data is text/html
607 if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
608 /*
609 * "text/html" can be encoded UCS2. It is recommended that
610 * documents transmitted as UCS2 always begin with a ZERO-WIDTH
611 * NON-BREAKING SPACE character (hexadecimal FEFF, also called
612 * Byte Order Mark (BOM)). Adding BOM can help other app to
613 * detect mozilla use UCS2 encoding when copy-paste.
614 */
615 guchar *buffer = (guchar *)
616 nsMemory::Alloc((len * sizeof(guchar)) + sizeof(char16_t));
617 if (!buffer)
618 return;
619 char16_t prefix = 0xFEFF;
620 memcpy(buffer, &prefix, sizeof(prefix));
621 memcpy(buffer + sizeof(prefix), primitive_data, len);
622 nsMemory::Free((guchar *)primitive_data);
623 primitive_data = (guchar *)buffer;
624 len += sizeof(prefix);
625 }
626
627 gtk_selection_data_set(aSelectionData, selectionTarget,
628 8, /* 8 bits in a unit */
629 (const guchar *)primitive_data, len);
630 nsMemory::Free(primitive_data);
631 }
632
633 g_free(target_name);
634
635 }
636
637 void
638 nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
639 {
640 int32_t whichClipboard;
641
642 // which clipboard?
643 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
644 whichClipboard = kSelectionClipboard;
645 else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
646 whichClipboard = kGlobalClipboard;
647 else
648 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
649
650 EmptyClipboard(whichClipboard);
651 }
652
653 void
654 clipboard_get_cb(GtkClipboard *aGtkClipboard,
655 GtkSelectionData *aSelectionData,
656 guint info,
657 gpointer user_data)
658 {
659 nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
660 aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
661 }
662
663 void
664 clipboard_clear_cb(GtkClipboard *aGtkClipboard,
665 gpointer user_data)
666 {
667 nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
668 aClipboard->SelectionClearEvent(aGtkClipboard);
669 }
670
671 /*
672 * when copy-paste, mozilla wants data encoded using UCS2,
673 * other app such as StarOffice use "text/html"(RFC2854).
674 * This function convert data(got from GTK clipboard)
675 * to data mozilla wanted.
676 *
677 * data from GTK clipboard can be 3 forms:
678 * 1. From current mozilla
679 * "text/html", charset = utf-16
680 * 2. From old version mozilla or mozilla-based app
681 * content("body" only), charset = utf-16
682 * 3. From other app who use "text/html" when copy-paste
683 * "text/html", has "charset" info
684 *
685 * data : got from GTK clipboard
686 * dataLength: got from GTK clipboard
687 * body : pass to Mozilla
688 * bodyLength: pass to Mozilla
689 */
690 void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
691 char16_t** unicodeData, int32_t& outUnicodeLen)
692 {
693 nsAutoCString charset;
694 GetHTMLCharset(data, dataLength, charset);// get charset of HTML
695 if (charset.EqualsLiteral("UTF-16")) {//current mozilla
696 outUnicodeLen = (dataLength / 2) - 1;
697 *unicodeData = reinterpret_cast<char16_t*>
698 (nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
699 sizeof(char16_t)));
700 if (*unicodeData) {
701 memcpy(*unicodeData, data + sizeof(char16_t),
702 outUnicodeLen * sizeof(char16_t));
703 (*unicodeData)[outUnicodeLen] = '\0';
704 }
705 } else if (charset.EqualsLiteral("UNKNOWN")) {
706 outUnicodeLen = 0;
707 return;
708 } else {
709 // app which use "text/html" to copy&paste
710 nsCOMPtr<nsIUnicodeDecoder> decoder;
711 nsresult rv;
712 // get the decoder
713 nsCOMPtr<nsICharsetConverterManager> ccm =
714 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
715 if (NS_FAILED(rv)) {
716 #ifdef DEBUG_CLIPBOARD
717 g_print(" can't get CHARSET CONVERTER MANAGER service\n");
718 #endif
719 outUnicodeLen = 0;
720 return;
721 }
722 rv = ccm->GetUnicodeDecoder(charset.get(), getter_AddRefs(decoder));
723 if (NS_FAILED(rv)) {
724 #ifdef DEBUG_CLIPBOARD
725 g_print(" get unicode decoder error\n");
726 #endif
727 outUnicodeLen = 0;
728 return;
729 }
730 // converting
731 decoder->GetMaxLength((const char *)data, dataLength, &outUnicodeLen);
732 // |outUnicodeLen| is number of chars
733 if (outUnicodeLen) {
734 *unicodeData = reinterpret_cast<char16_t*>
735 (nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
736 sizeof(char16_t)));
737 if (*unicodeData) {
738 int32_t numberTmp = dataLength;
739 decoder->Convert((const char *)data, &numberTmp,
740 *unicodeData, &outUnicodeLen);
741 #ifdef DEBUG_CLIPBOARD
742 if (numberTmp != dataLength)
743 printf("didn't consume all the bytes\n");
744 #endif
745 // null terminate. Convert() doesn't do it for us
746 (*unicodeData)[outUnicodeLen] = '\0';
747 }
748 } // if valid length
749 }
750 }
751
752 /*
753 * get "charset" information from clipboard data
754 * return value can be:
755 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
756 * 2. "UNKNOWN": mozilla can't detect what encode it use
757 * 3. other: "text/html" with other charset than utf-16
758 */
759 void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str)
760 {
761 // if detect "FFFE" or "FEFF", assume UTF-16
762 char16_t* beginChar = (char16_t*)data;
763 if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
764 str.AssignLiteral("UTF-16");
765 return;
766 }
767 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
768 const nsDependentCString htmlStr((const char *)data, dataLength);
769 nsACString::const_iterator start, end;
770 htmlStr.BeginReading(start);
771 htmlStr.EndReading(end);
772 nsACString::const_iterator valueStart(start), valueEnd(start);
773
774 if (CaseInsensitiveFindInReadable(
775 NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
776 start, end)) {
777 start = end;
778 htmlStr.EndReading(end);
779
780 if (CaseInsensitiveFindInReadable(
781 NS_LITERAL_CSTRING("charset="),
782 start, end)) {
783 valueStart = end;
784 start = end;
785 htmlStr.EndReading(end);
786
787 if (FindCharInReadable('"', start, end))
788 valueEnd = start;
789 }
790 }
791 // find "charset" in HTML
792 if (valueStart != valueEnd) {
793 str = Substring(valueStart, valueEnd);
794 ToUpperCase(str);
795 #ifdef DEBUG_CLIPBOARD
796 printf("Charset of HTML = %s\n", charsetUpperStr.get());
797 #endif
798 return;
799 }
800 str.AssignLiteral("UNKNOWN");
801 }
802
803 static void
804 DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
805 {
806 GdkEvent event;
807 event.selection.type = GDK_SELECTION_NOTIFY;
808 event.selection.window = gtk_widget_get_window(widget);
809 event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
810 event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
811 event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
812 event.selection.time = xevent->xselection.time;
813
814 gtk_widget_event(widget, &event);
815 }
816
817 static void
818 DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
819 {
820 GdkWindow *window = gtk_widget_get_window(widget);
821 if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
822 GdkEvent event;
823 event.property.type = GDK_PROPERTY_NOTIFY;
824 event.property.window = window;
825 event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
826 event.property.time = xevent->xproperty.time;
827 event.property.state = xevent->xproperty.state;
828
829 gtk_widget_event(widget, &event);
830 }
831 }
832
833 struct checkEventContext
834 {
835 GtkWidget *cbWidget;
836 Atom selAtom;
837 };
838
839 static Bool
840 checkEventProc(Display *display, XEvent *event, XPointer arg)
841 {
842 checkEventContext *context = (checkEventContext *) arg;
843
844 if (event->xany.type == SelectionNotify ||
845 (event->xany.type == PropertyNotify &&
846 event->xproperty.atom == context->selAtom)) {
847
848 GdkWindow *cbWindow =
849 gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
850 event->xany.window);
851 if (cbWindow) {
852 GtkWidget *cbWidget = nullptr;
853 gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
854 if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
855 context->cbWidget = cbWidget;
856 return True;
857 }
858 }
859 }
860
861 return False;
862 }
863
864 // Idle timeout for receiving selection and property notify events (microsec)
865 static const int kClipboardTimeout = 500000;
866
867 static gchar* CopyRetrievedData(const gchar *aData)
868 {
869 return g_strdup(aData);
870 }
871
872 static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
873 {
874 // A negative length indicates that retrieving the data failed.
875 return gtk_selection_data_get_length(aData) >= 0 ?
876 gtk_selection_data_copy(aData) : nullptr;
877 }
878
879 class RetrievalContext {
880 ~RetrievalContext()
881 {
882 MOZ_ASSERT(!mData, "Wait() wasn't called");
883 }
884
885 public:
886 NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
887 enum State { INITIAL, COMPLETED, TIMED_OUT };
888
889 RetrievalContext() : mState(INITIAL), mData(nullptr) {}
890
891 /**
892 * Call this when data has been retrieved.
893 */
894 template <class T> void Complete(T *aData)
895 {
896 if (mState == INITIAL) {
897 mState = COMPLETED;
898 mData = CopyRetrievedData(aData);
899 } else {
900 // Already timed out
901 MOZ_ASSERT(mState == TIMED_OUT);
902 }
903 }
904
905 /**
906 * Spins X event loop until timing out or being completed. Returns
907 * null if we time out, otherwise returns the completed data (passing
908 * ownership to caller).
909 */
910 void *Wait();
911
912 protected:
913 State mState;
914 void* mData;
915 };
916
917 void *
918 RetrievalContext::Wait()
919 {
920 if (mState == COMPLETED) { // the request completed synchronously
921 void *data = mData;
922 mData = nullptr;
923 return data;
924 }
925
926 Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) ;
927 checkEventContext context;
928 context.cbWidget = nullptr;
929 context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
930 FALSE));
931
932 // Send X events which are relevant to the ongoing selection retrieval
933 // to the clipboard widget. Wait until either the operation completes, or
934 // we hit our timeout. All other X events remain queued.
935
936 int select_result;
937
938 int cnumber = ConnectionNumber(xDisplay);
939 fd_set select_set;
940 FD_ZERO(&select_set);
941 FD_SET(cnumber, &select_set);
942 ++cnumber;
943 TimeStamp start = TimeStamp::Now();
944
945 do {
946 XEvent xevent;
947
948 while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
949 (XPointer) &context)) {
950
951 if (xevent.xany.type == SelectionNotify)
952 DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
953 else
954 DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
955
956 if (mState == COMPLETED) {
957 void *data = mData;
958 mData = nullptr;
959 return data;
960 }
961 }
962
963 TimeStamp now = TimeStamp::Now();
964 struct timeval tv;
965 tv.tv_sec = 0;
966 tv.tv_usec = std::max<int32_t>(0,
967 kClipboardTimeout - (now - start).ToMicroseconds());
968 select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
969 } while (select_result == 1 ||
970 (select_result == -1 && errno == EINTR));
971
972 #ifdef DEBUG_CLIPBOARD
973 printf("exceeded clipboard timeout\n");
974 #endif
975 mState = TIMED_OUT;
976 return nullptr;
977 }
978
979 static void
980 clipboard_contents_received(GtkClipboard *clipboard,
981 GtkSelectionData *selection_data,
982 gpointer data)
983 {
984 RetrievalContext *context = static_cast<RetrievalContext*>(data);
985 context->Complete(selection_data);
986 context->Release();
987 }
988
989 static GtkSelectionData *
990 wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
991 {
992 RefPtr<RetrievalContext> context = new RetrievalContext();
993 // Balanced by Release in clipboard_contents_received
994 context->AddRef();
995 gtk_clipboard_request_contents(clipboard, target,
996 clipboard_contents_received,
997 context.get());
998 return static_cast<GtkSelectionData*>(context->Wait());
999 }
1000
1001 static void
1002 clipboard_text_received(GtkClipboard *clipboard,
1003 const gchar *text,
1004 gpointer data)
1005 {
1006 RetrievalContext *context = static_cast<RetrievalContext*>(data);
1007 context->Complete(text);
1008 context->Release();
1009 }
1010
1011 static gchar *
1012 wait_for_text(GtkClipboard *clipboard)
1013 {
1014 RefPtr<RetrievalContext> context = new RetrievalContext();
1015 // Balanced by Release in clipboard_text_received
1016 context->AddRef();
1017 gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
1018 return static_cast<gchar*>(context->Wait());
1019 }

mercurial