|
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 } |