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