/src/mozilla-central/widget/gtk/nsClipboard.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsArrayUtils.h" |
11 | | #include "nsClipboard.h" |
12 | | #include "nsClipboardX11.h" |
13 | | #if defined(MOZ_WAYLAND) |
14 | | #include "nsClipboardWayland.h" |
15 | | #endif |
16 | | #include "HeadlessClipboard.h" |
17 | | #include "nsSupportsPrimitives.h" |
18 | | #include "nsString.h" |
19 | | #include "nsReadableUtils.h" |
20 | | #include "nsPrimitiveHelpers.h" |
21 | | #include "nsIServiceManager.h" |
22 | | #include "nsImageToPixbuf.h" |
23 | | #include "nsStringStream.h" |
24 | | #include "nsIObserverService.h" |
25 | | #include "mozilla/Services.h" |
26 | | #include "mozilla/RefPtr.h" |
27 | | #include "mozilla/TimeStamp.h" |
28 | | |
29 | | #include "imgIContainer.h" |
30 | | |
31 | | #include <gtk/gtk.h> |
32 | | #include <gtk/gtkx.h> |
33 | | |
34 | | #include "mozilla/Encoding.h" |
35 | | |
36 | | |
37 | | using namespace mozilla; |
38 | | |
39 | | // Idle timeout for receiving selection and property notify events (microsec) |
40 | | const int kClipboardTimeout = 500000; |
41 | | |
42 | | // Callback when someone asks us for the data |
43 | | void |
44 | | clipboard_get_cb(GtkClipboard *aGtkClipboard, |
45 | | GtkSelectionData *aSelectionData, |
46 | | guint info, |
47 | | gpointer user_data); |
48 | | |
49 | | // Callback when someone asks us to clear a clipboard |
50 | | void |
51 | | clipboard_clear_cb(GtkClipboard *aGtkClipboard, |
52 | | gpointer user_data); |
53 | | |
54 | | static void |
55 | | ConvertHTMLtoUCS2 (const char* data, |
56 | | int32_t dataLength, |
57 | | char16_t **unicodeData, |
58 | | int32_t &outUnicodeLen); |
59 | | |
60 | | static void |
61 | | GetHTMLCharset (const char* data, int32_t dataLength, nsCString& str); |
62 | | |
63 | | GdkAtom |
64 | | GetSelectionAtom(int32_t aWhichClipboard) |
65 | 0 | { |
66 | 0 | if (aWhichClipboard == nsIClipboard::kGlobalClipboard) |
67 | 0 | return GDK_SELECTION_CLIPBOARD; |
68 | 0 | |
69 | 0 | return GDK_SELECTION_PRIMARY; |
70 | 0 | } |
71 | | |
72 | | nsClipboard::nsClipboard() |
73 | 0 | { |
74 | 0 | } |
75 | | |
76 | | nsClipboard::~nsClipboard() |
77 | 0 | { |
78 | 0 | // We have to clear clipboard before gdk_display_close() call. |
79 | 0 | // See bug 531580 for details. |
80 | 0 | if (mGlobalTransferable) { |
81 | 0 | gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); |
82 | 0 | } |
83 | 0 | if (mSelectionTransferable) { |
84 | 0 | gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); |
85 | 0 | } |
86 | 0 | } |
87 | | |
88 | | NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard) |
89 | | |
90 | | nsresult |
91 | | nsClipboard::Init(void) |
92 | 0 | { |
93 | 0 | GdkDisplay *display = gdk_display_get_default(); |
94 | 0 |
|
95 | 0 | // Create a nsRetrievalContext. If there's no default display |
96 | 0 | // create the X11 one as a fallback. |
97 | 0 | if (!display || GDK_IS_X11_DISPLAY(display)) { |
98 | 0 | mContext = new nsRetrievalContextX11(); |
99 | | #if defined(MOZ_WAYLAND) |
100 | | } else { |
101 | | mContext = new nsRetrievalContextWayland(); |
102 | | #endif |
103 | | } |
104 | 0 | NS_ASSERTION(mContext, "Missing nsRetrievalContext for nsClipboard!"); |
105 | 0 |
|
106 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
107 | 0 | if (os) { |
108 | 0 | os->AddObserver(this, "quit-application", false); |
109 | 0 | os->AddObserver(this, "xpcom-shutdown", false); |
110 | 0 | } |
111 | 0 |
|
112 | 0 | return NS_OK; |
113 | 0 | } |
114 | | |
115 | | |
116 | | nsresult |
117 | | nsClipboard::Store(void) |
118 | 0 | { |
119 | 0 | if (mGlobalTransferable) { |
120 | 0 | GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); |
121 | 0 | gtk_clipboard_store(clipboard); |
122 | 0 | } |
123 | 0 | return NS_OK; |
124 | 0 | } |
125 | | |
126 | | NS_IMETHODIMP |
127 | | nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, |
128 | | const char16_t *aData) |
129 | 0 | { |
130 | 0 | Store(); |
131 | 0 | return NS_OK; |
132 | 0 | } |
133 | | |
134 | | NS_IMETHODIMP |
135 | | nsClipboard::SetData(nsITransferable *aTransferable, |
136 | | nsIClipboardOwner *aOwner, int32_t aWhichClipboard) |
137 | 0 | { |
138 | 0 | // See if we can short cut |
139 | 0 | if ((aWhichClipboard == kGlobalClipboard && |
140 | 0 | aTransferable == mGlobalTransferable.get() && |
141 | 0 | aOwner == mGlobalOwner.get()) || |
142 | 0 | (aWhichClipboard == kSelectionClipboard && |
143 | 0 | aTransferable == mSelectionTransferable.get() && |
144 | 0 | aOwner == mSelectionOwner.get())) { |
145 | 0 | return NS_OK; |
146 | 0 | } |
147 | 0 | |
148 | 0 | // Clear out the clipboard in order to set the new data |
149 | 0 | EmptyClipboard(aWhichClipboard); |
150 | 0 |
|
151 | 0 | // List of suported targets |
152 | 0 | GtkTargetList *list = gtk_target_list_new(nullptr, 0); |
153 | 0 |
|
154 | 0 | // Get the types of supported flavors |
155 | 0 | nsCOMPtr<nsIArray> flavors; |
156 | 0 |
|
157 | 0 | nsresult rv = |
158 | 0 | aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors)); |
159 | 0 | if (!flavors || NS_FAILED(rv)) |
160 | 0 | return NS_ERROR_FAILURE; |
161 | 0 | |
162 | 0 | // Add all the flavors to this widget's supported type. |
163 | 0 | bool imagesAdded = false; |
164 | 0 | uint32_t count; |
165 | 0 | flavors->GetLength(&count); |
166 | 0 | for (uint32_t i=0; i < count; i++) { |
167 | 0 | nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavors, i); |
168 | 0 |
|
169 | 0 | if (flavor) { |
170 | 0 | nsCString flavorStr; |
171 | 0 | flavor->ToString(getter_Copies(flavorStr)); |
172 | 0 |
|
173 | 0 | // special case text/unicode since we can handle all of |
174 | 0 | // the string types |
175 | 0 | if (flavorStr.EqualsLiteral(kUnicodeMime)) { |
176 | 0 | gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0); |
177 | 0 | gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0); |
178 | 0 | gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0); |
179 | 0 | gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0); |
180 | 0 | continue; |
181 | 0 | } |
182 | 0 | |
183 | 0 | if (flavorStr.EqualsLiteral(kNativeImageMime) || |
184 | 0 | flavorStr.EqualsLiteral(kPNGImageMime) || |
185 | 0 | flavorStr.EqualsLiteral(kJPEGImageMime) || |
186 | 0 | flavorStr.EqualsLiteral(kJPGImageMime) || |
187 | 0 | flavorStr.EqualsLiteral(kGIFImageMime)) { |
188 | 0 | // don't bother adding image targets twice |
189 | 0 | if (!imagesAdded) { |
190 | 0 | // accept any writable image type |
191 | 0 | gtk_target_list_add_image_targets(list, 0, TRUE); |
192 | 0 | imagesAdded = true; |
193 | 0 | } |
194 | 0 | continue; |
195 | 0 | } |
196 | 0 |
|
197 | 0 | // Add this to our list of valid targets |
198 | 0 | GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE); |
199 | 0 | gtk_target_list_add(list, atom, 0, 0); |
200 | 0 | } |
201 | 0 | } |
202 | 0 |
|
203 | 0 | // Get GTK clipboard (CLIPBOARD or PRIMARY) |
204 | 0 | GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)); |
205 | 0 |
|
206 | 0 | gint numTargets; |
207 | 0 | GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets); |
208 | 0 |
|
209 | 0 | // Set getcallback and request to store data after an application exit |
210 | 0 | if (gtkTargets && |
211 | 0 | gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets, |
212 | 0 | clipboard_get_cb, clipboard_clear_cb, this)) |
213 | 0 | { |
214 | 0 | // We managed to set-up the clipboard so update internal state |
215 | 0 | // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb() |
216 | 0 | // which reset our internal state |
217 | 0 | if (aWhichClipboard == kSelectionClipboard) { |
218 | 0 | mSelectionOwner = aOwner; |
219 | 0 | mSelectionTransferable = aTransferable; |
220 | 0 | } |
221 | 0 | else { |
222 | 0 | mGlobalOwner = aOwner; |
223 | 0 | mGlobalTransferable = aTransferable; |
224 | 0 | gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets); |
225 | 0 | } |
226 | 0 |
|
227 | 0 | rv = NS_OK; |
228 | 0 | } |
229 | 0 | else { |
230 | 0 | rv = NS_ERROR_FAILURE; |
231 | 0 | } |
232 | 0 |
|
233 | 0 | gtk_target_table_free(gtkTargets, numTargets); |
234 | 0 | gtk_target_list_unref(list); |
235 | 0 |
|
236 | 0 | return rv; |
237 | 0 | } |
238 | | |
239 | | void |
240 | | nsClipboard::SetTransferableData(nsITransferable* aTransferable, |
241 | | nsCString& aFlavor, |
242 | | const char* aClipboardData, |
243 | | uint32_t aClipboardDataLength) |
244 | 0 | { |
245 | 0 | nsCOMPtr<nsISupports> wrapper; |
246 | 0 | nsPrimitiveHelpers::CreatePrimitiveForData(aFlavor, |
247 | 0 | aClipboardData, |
248 | 0 | aClipboardDataLength, |
249 | 0 | getter_AddRefs(wrapper)); |
250 | 0 | aTransferable->SetTransferData(aFlavor.get(), |
251 | 0 | wrapper, aClipboardDataLength); |
252 | 0 | } |
253 | | |
254 | | NS_IMETHODIMP |
255 | | nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard) |
256 | 0 | { |
257 | 0 | if (!aTransferable) |
258 | 0 | return NS_ERROR_FAILURE; |
259 | 0 | |
260 | 0 | // Get a list of flavors this transferable can import |
261 | 0 | nsCOMPtr<nsIArray> flavors; |
262 | 0 | nsresult rv; |
263 | 0 | rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors)); |
264 | 0 | if (!flavors || NS_FAILED(rv)) |
265 | 0 | return NS_ERROR_FAILURE; |
266 | 0 | |
267 | 0 | uint32_t count; |
268 | 0 | flavors->GetLength(&count); |
269 | 0 | for (uint32_t i=0; i < count; i++) { |
270 | 0 | nsCOMPtr<nsISupportsCString> currentFlavor; |
271 | 0 | currentFlavor = do_QueryElementAt(flavors, i); |
272 | 0 | if (!currentFlavor) |
273 | 0 | continue; |
274 | 0 | |
275 | 0 | nsCString flavorStr; |
276 | 0 | currentFlavor->ToString(getter_Copies(flavorStr)); |
277 | 0 |
|
278 | 0 | if (flavorStr.EqualsLiteral(kJPEGImageMime) || |
279 | 0 | flavorStr.EqualsLiteral(kJPGImageMime) || |
280 | 0 | flavorStr.EqualsLiteral(kPNGImageMime) || |
281 | 0 | flavorStr.EqualsLiteral(kGIFImageMime)) { |
282 | 0 | // Emulate support for image/jpg |
283 | 0 | if (flavorStr.EqualsLiteral(kJPGImageMime)) { |
284 | 0 | flavorStr.Assign(kJPEGImageMime); |
285 | 0 | } |
286 | 0 |
|
287 | 0 | uint32_t clipboardDataLength; |
288 | 0 | const char* clipboardData = |
289 | 0 | mContext->GetClipboardData(flavorStr.get(), |
290 | 0 | aWhichClipboard, |
291 | 0 | &clipboardDataLength); |
292 | 0 | if (!clipboardData) |
293 | 0 | continue; |
294 | 0 | |
295 | 0 | nsCOMPtr<nsIInputStream> byteStream; |
296 | 0 | NS_NewByteInputStream(getter_AddRefs(byteStream), |
297 | 0 | clipboardData, |
298 | 0 | clipboardDataLength, |
299 | 0 | NS_ASSIGNMENT_COPY); |
300 | 0 | aTransferable->SetTransferData(flavorStr.get(), byteStream, |
301 | 0 | sizeof(nsIInputStream*)); |
302 | 0 |
|
303 | 0 | mContext->ReleaseClipboardData(clipboardData); |
304 | 0 | return NS_OK; |
305 | 0 | } |
306 | 0 | |
307 | 0 | // Special case text/unicode since we can convert any |
308 | 0 | // string into text/unicode |
309 | 0 | if (flavorStr.EqualsLiteral(kUnicodeMime)) { |
310 | 0 | const char* clipboardData = |
311 | 0 | mContext->GetClipboardText(aWhichClipboard); |
312 | 0 | if (!clipboardData) { |
313 | 0 | // If the type was text/unicode and we couldn't get |
314 | 0 | // text off the clipboard, run the next loop |
315 | 0 | // iteration. |
316 | 0 | continue; |
317 | 0 | } |
318 | 0 | |
319 | 0 | // Convert utf-8 into our unicode format. |
320 | 0 | NS_ConvertUTF8toUTF16 ucs2string(clipboardData); |
321 | 0 | const char* unicodeData = (const char *)ToNewUnicode(ucs2string); |
322 | 0 | uint32_t unicodeDataLength = ucs2string.Length() * 2; |
323 | 0 | SetTransferableData(aTransferable, flavorStr, |
324 | 0 | unicodeData, unicodeDataLength); |
325 | 0 | free((void *)unicodeData); |
326 | 0 |
|
327 | 0 | mContext->ReleaseClipboardData(clipboardData); |
328 | 0 | return NS_OK; |
329 | 0 | } |
330 | 0 | |
331 | 0 | |
332 | 0 | uint32_t clipboardDataLength; |
333 | 0 | const char* clipboardData = mContext->GetClipboardData( |
334 | 0 | flavorStr.get(), aWhichClipboard, &clipboardDataLength); |
335 | 0 |
|
336 | 0 | if (clipboardData) { |
337 | 0 | // Special case text/html since we can convert into UCS2 |
338 | 0 | if (flavorStr.EqualsLiteral(kHTMLMime)) { |
339 | 0 | char16_t* htmlBody = nullptr; |
340 | 0 | int32_t htmlBodyLen = 0; |
341 | 0 | // Convert text/html into our unicode format |
342 | 0 | ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, |
343 | 0 | &htmlBody, htmlBodyLen); |
344 | 0 |
|
345 | 0 | // Try next data format? |
346 | 0 | if (!htmlBodyLen) { |
347 | 0 | mContext->ReleaseClipboardData(clipboardData); |
348 | 0 | continue; |
349 | 0 | } |
350 | 0 | |
351 | 0 | SetTransferableData(aTransferable, flavorStr, |
352 | 0 | (const char*)htmlBody, htmlBodyLen * 2); |
353 | 0 | free(htmlBody); |
354 | 0 | } else { |
355 | 0 | SetTransferableData(aTransferable, flavorStr, |
356 | 0 | clipboardData, clipboardDataLength); |
357 | 0 | } |
358 | 0 |
|
359 | 0 | mContext->ReleaseClipboardData(clipboardData); |
360 | 0 | return NS_OK; |
361 | 0 | } |
362 | 0 | } |
363 | 0 |
|
364 | 0 | return NS_OK; |
365 | 0 | } |
366 | | |
367 | | NS_IMETHODIMP |
368 | | nsClipboard::EmptyClipboard(int32_t aWhichClipboard) |
369 | 0 | { |
370 | 0 | if (aWhichClipboard == kSelectionClipboard) { |
371 | 0 | if (mSelectionOwner) { |
372 | 0 | mSelectionOwner->LosingOwnership(mSelectionTransferable); |
373 | 0 | mSelectionOwner = nullptr; |
374 | 0 | } |
375 | 0 | mSelectionTransferable = nullptr; |
376 | 0 | } |
377 | 0 | else { |
378 | 0 | if (mGlobalOwner) { |
379 | 0 | mGlobalOwner->LosingOwnership(mGlobalTransferable); |
380 | 0 | mGlobalOwner = nullptr; |
381 | 0 | } |
382 | 0 | mGlobalTransferable = nullptr; |
383 | 0 | } |
384 | 0 |
|
385 | 0 | return NS_OK; |
386 | 0 | } |
387 | | |
388 | | NS_IMETHODIMP |
389 | | nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength, |
390 | | int32_t aWhichClipboard, bool *_retval) |
391 | 0 | { |
392 | 0 | if (!aFlavorList || !_retval) |
393 | 0 | return NS_ERROR_NULL_POINTER; |
394 | 0 | |
395 | 0 | *_retval = false; |
396 | 0 |
|
397 | 0 | int targetNums; |
398 | 0 | GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums); |
399 | 0 | if (!targets) |
400 | 0 | return NS_OK; |
401 | 0 | |
402 | 0 | // Walk through the provided types and try to match it to a |
403 | 0 | // provided type. |
404 | 0 | for (uint32_t i = 0; i < aLength && !*_retval; i++) { |
405 | 0 | // We special case text/unicode here. |
406 | 0 | if (!strcmp(aFlavorList[i], kUnicodeMime) && |
407 | 0 | gtk_targets_include_text(targets, targetNums)) { |
408 | 0 | *_retval = true; |
409 | 0 | break; |
410 | 0 | } |
411 | 0 | |
412 | 0 | for (int32_t j = 0; j < targetNums; j++) { |
413 | 0 | gchar *atom_name = gdk_atom_name(targets[j]); |
414 | 0 | if (!atom_name) |
415 | 0 | continue; |
416 | 0 | |
417 | 0 | if (!strcmp(atom_name, aFlavorList[i])) |
418 | 0 | *_retval = true; |
419 | 0 |
|
420 | 0 | // X clipboard supports image/jpeg, but we want to emulate support |
421 | 0 | // for image/jpg as well |
422 | 0 | if (!strcmp(aFlavorList[i], kJPGImageMime) && |
423 | 0 | !strcmp(atom_name, kJPEGImageMime)) { |
424 | 0 | *_retval = true; |
425 | 0 | } |
426 | 0 |
|
427 | 0 | g_free(atom_name); |
428 | 0 |
|
429 | 0 | if (*_retval) |
430 | 0 | break; |
431 | 0 | } |
432 | 0 | } |
433 | 0 |
|
434 | 0 | g_free(targets); |
435 | 0 | return NS_OK; |
436 | 0 | } |
437 | | |
438 | | NS_IMETHODIMP |
439 | | nsClipboard::SupportsSelectionClipboard(bool *_retval) |
440 | 0 | { |
441 | 0 | *_retval = mContext->HasSelectionSupport(); |
442 | 0 | return NS_OK; |
443 | 0 | } |
444 | | |
445 | | NS_IMETHODIMP |
446 | | nsClipboard::SupportsFindClipboard(bool* _retval) |
447 | 0 | { |
448 | 0 | *_retval = false; |
449 | 0 | return NS_OK; |
450 | 0 | } |
451 | | |
452 | | nsITransferable * |
453 | | nsClipboard::GetTransferable(int32_t aWhichClipboard) |
454 | 0 | { |
455 | 0 | nsITransferable *retval; |
456 | 0 |
|
457 | 0 | if (aWhichClipboard == kSelectionClipboard) |
458 | 0 | retval = mSelectionTransferable.get(); |
459 | 0 | else |
460 | 0 | retval = mGlobalTransferable.get(); |
461 | 0 |
|
462 | 0 | return retval; |
463 | 0 | } |
464 | | |
465 | | void |
466 | | nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard, |
467 | | GtkSelectionData *aSelectionData) |
468 | 0 | { |
469 | 0 | // Someone has asked us to hand them something. The first thing |
470 | 0 | // that we want to do is see if that something includes text. If |
471 | 0 | // it does, try to give it text/unicode after converting it to |
472 | 0 | // utf-8. |
473 | 0 |
|
474 | 0 | int32_t whichClipboard; |
475 | 0 |
|
476 | 0 | // which clipboard? |
477 | 0 | GdkAtom selection = gtk_selection_data_get_selection(aSelectionData); |
478 | 0 | if (selection == GDK_SELECTION_PRIMARY) |
479 | 0 | whichClipboard = kSelectionClipboard; |
480 | 0 | else if (selection == GDK_SELECTION_CLIPBOARD) |
481 | 0 | whichClipboard = kGlobalClipboard; |
482 | 0 | else |
483 | 0 | return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF |
484 | 0 | |
485 | 0 | nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard); |
486 | 0 | if (!trans) { |
487 | 0 | // We have nothing to serve |
488 | | #ifdef DEBUG_CLIPBOARD |
489 | | printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n", |
490 | | whichClipboard == kSelectionClipboard ? "Selection" : "Global"); |
491 | | #endif |
492 | | return; |
493 | 0 | } |
494 | 0 |
|
495 | 0 | nsresult rv; |
496 | 0 | nsCOMPtr<nsISupports> item; |
497 | 0 | uint32_t len; |
498 | 0 |
|
499 | 0 | GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData); |
500 | 0 |
|
501 | 0 | // Check to see if the selection data includes any of the string |
502 | 0 | // types that we support. |
503 | 0 | if (selectionTarget == gdk_atom_intern ("STRING", FALSE) || |
504 | 0 | selectionTarget == gdk_atom_intern ("TEXT", FALSE) || |
505 | 0 | selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) || |
506 | 0 | selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) { |
507 | 0 | // Try to convert our internal type into a text string. Get |
508 | 0 | // the transferable for this clipboard and try to get the |
509 | 0 | // text/unicode type for it. |
510 | 0 | rv = trans->GetTransferData("text/unicode", getter_AddRefs(item), |
511 | 0 | &len); |
512 | 0 | if (!item || NS_FAILED(rv)) |
513 | 0 | return; |
514 | 0 | |
515 | 0 | nsCOMPtr<nsISupportsString> wideString; |
516 | 0 | wideString = do_QueryInterface(item); |
517 | 0 | if (!wideString) |
518 | 0 | return; |
519 | 0 | |
520 | 0 | nsAutoString ucs2string; |
521 | 0 | wideString->GetData(ucs2string); |
522 | 0 | char *utf8string = ToNewUTF8String(ucs2string); |
523 | 0 | if (!utf8string) |
524 | 0 | return; |
525 | 0 | |
526 | 0 | gtk_selection_data_set_text (aSelectionData, utf8string, |
527 | 0 | strlen(utf8string)); |
528 | 0 |
|
529 | 0 | free(utf8string); |
530 | 0 | return; |
531 | 0 | } |
532 | 0 | |
533 | 0 | // Check to see if the selection data is an image type |
534 | 0 | if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) { |
535 | 0 | // Look through our transfer data for the image |
536 | 0 | static const char* const imageMimeTypes[] = { |
537 | 0 | kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime }; |
538 | 0 | nsCOMPtr<nsISupports> imageItem; |
539 | 0 | nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive; |
540 | 0 | for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) { |
541 | 0 | rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem), &len); |
542 | 0 | ptrPrimitive = do_QueryInterface(imageItem); |
543 | 0 | } |
544 | 0 | if (!ptrPrimitive) |
545 | 0 | return; |
546 | 0 | |
547 | 0 | nsCOMPtr<nsISupports> primitiveData; |
548 | 0 | ptrPrimitive->GetData(getter_AddRefs(primitiveData)); |
549 | 0 | nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData)); |
550 | 0 | if (!image) // Not getting an image for an image mime type!? |
551 | 0 | return; |
552 | 0 | |
553 | 0 | GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image); |
554 | 0 | if (!pixbuf) |
555 | 0 | return; |
556 | 0 | |
557 | 0 | gtk_selection_data_set_pixbuf(aSelectionData, pixbuf); |
558 | 0 | g_object_unref(pixbuf); |
559 | 0 | return; |
560 | 0 | } |
561 | 0 | |
562 | 0 | // Try to match up the selection data target to something our |
563 | 0 | // transferable provides. |
564 | 0 | gchar *target_name = gdk_atom_name(selectionTarget); |
565 | 0 | if (!target_name) |
566 | 0 | return; |
567 | 0 | |
568 | 0 | rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len); |
569 | 0 | // nothing found? |
570 | 0 | if (!item || NS_FAILED(rv)) { |
571 | 0 | g_free(target_name); |
572 | 0 | return; |
573 | 0 | } |
574 | 0 | |
575 | 0 | void *primitive_data = nullptr; |
576 | 0 | nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name), |
577 | 0 | item, &primitive_data, len); |
578 | 0 |
|
579 | 0 | if (primitive_data) { |
580 | 0 | // Check to see if the selection data is text/html |
581 | 0 | if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) { |
582 | 0 | /* |
583 | 0 | * "text/html" can be encoded UCS2. It is recommended that |
584 | 0 | * documents transmitted as UCS2 always begin with a ZERO-WIDTH |
585 | 0 | * NON-BREAKING SPACE character (hexadecimal FEFF, also called |
586 | 0 | * Byte Order Mark (BOM)). Adding BOM can help other app to |
587 | 0 | * detect mozilla use UCS2 encoding when copy-paste. |
588 | 0 | */ |
589 | 0 | guchar *buffer = (guchar *) |
590 | 0 | g_malloc((len * sizeof(guchar)) + sizeof(char16_t)); |
591 | 0 | if (!buffer) |
592 | 0 | return; |
593 | 0 | char16_t prefix = 0xFEFF; |
594 | 0 | memcpy(buffer, &prefix, sizeof(prefix)); |
595 | 0 | memcpy(buffer + sizeof(prefix), primitive_data, len); |
596 | 0 | g_free((guchar *)primitive_data); |
597 | 0 | primitive_data = (guchar *)buffer; |
598 | 0 | len += sizeof(prefix); |
599 | 0 | } |
600 | 0 |
|
601 | 0 | gtk_selection_data_set(aSelectionData, selectionTarget, |
602 | 0 | 8, /* 8 bits in a unit */ |
603 | 0 | (const guchar *)primitive_data, len); |
604 | 0 | g_free(primitive_data); |
605 | 0 | } |
606 | 0 |
|
607 | 0 | g_free(target_name); |
608 | 0 |
|
609 | 0 | } |
610 | | |
611 | | void |
612 | | nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard) |
613 | 0 | { |
614 | 0 | int32_t whichClipboard; |
615 | 0 |
|
616 | 0 | // which clipboard? |
617 | 0 | if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY)) |
618 | 0 | whichClipboard = kSelectionClipboard; |
619 | 0 | else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)) |
620 | 0 | whichClipboard = kGlobalClipboard; |
621 | 0 | else |
622 | 0 | return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF |
623 | 0 | |
624 | 0 | EmptyClipboard(whichClipboard); |
625 | 0 | } |
626 | | |
627 | | void |
628 | | clipboard_get_cb(GtkClipboard *aGtkClipboard, |
629 | | GtkSelectionData *aSelectionData, |
630 | | guint info, |
631 | | gpointer user_data) |
632 | 0 | { |
633 | 0 | nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data); |
634 | 0 | aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData); |
635 | 0 | } |
636 | | |
637 | | void |
638 | | clipboard_clear_cb(GtkClipboard *aGtkClipboard, |
639 | | gpointer user_data) |
640 | 0 | { |
641 | 0 | nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data); |
642 | 0 | aClipboard->SelectionClearEvent(aGtkClipboard); |
643 | 0 | } |
644 | | |
645 | | /* |
646 | | * when copy-paste, mozilla wants data encoded using UCS2, |
647 | | * other app such as StarOffice use "text/html"(RFC2854). |
648 | | * This function convert data(got from GTK clipboard) |
649 | | * to data mozilla wanted. |
650 | | * |
651 | | * data from GTK clipboard can be 3 forms: |
652 | | * 1. From current mozilla |
653 | | * "text/html", charset = utf-16 |
654 | | * 2. From old version mozilla or mozilla-based app |
655 | | * content("body" only), charset = utf-16 |
656 | | * 3. From other app who use "text/html" when copy-paste |
657 | | * "text/html", has "charset" info |
658 | | * |
659 | | * data : got from GTK clipboard |
660 | | * dataLength: got from GTK clipboard |
661 | | * body : pass to Mozilla |
662 | | * bodyLength: pass to Mozilla |
663 | | */ |
664 | | void ConvertHTMLtoUCS2(const char* data, int32_t dataLength, |
665 | | char16_t** unicodeData, int32_t& outUnicodeLen) |
666 | 0 | { |
667 | 0 | nsAutoCString charset; |
668 | 0 | GetHTMLCharset(data, dataLength, charset);// get charset of HTML |
669 | 0 | if (charset.EqualsLiteral("UTF-16")) {//current mozilla |
670 | 0 | outUnicodeLen = (dataLength / 2) - 1; |
671 | 0 | *unicodeData = |
672 | 0 | reinterpret_cast<char16_t*> |
673 | 0 | (moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t))); |
674 | 0 | memcpy(*unicodeData, data + sizeof(char16_t), |
675 | 0 | outUnicodeLen * sizeof(char16_t)); |
676 | 0 | (*unicodeData)[outUnicodeLen] = '\0'; |
677 | 0 | } else if (charset.EqualsLiteral("UNKNOWN")) { |
678 | 0 | outUnicodeLen = 0; |
679 | 0 | return; |
680 | 0 | } else { |
681 | 0 | // app which use "text/html" to copy&paste |
682 | 0 | // get the decoder |
683 | 0 | auto encoding = Encoding::ForLabelNoReplacement(charset); |
684 | 0 | if (!encoding) { |
685 | | #ifdef DEBUG_CLIPBOARD |
686 | | g_print(" get unicode decoder error\n"); |
687 | | #endif |
688 | | outUnicodeLen = 0; |
689 | 0 | return; |
690 | 0 | } |
691 | 0 | auto decoder = encoding->NewDecoder(); |
692 | 0 | CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(dataLength); |
693 | 0 | if (!needed.isValid() || needed.value() > INT32_MAX) { |
694 | 0 | outUnicodeLen = 0; |
695 | 0 | return; |
696 | 0 | } |
697 | 0 | |
698 | 0 | outUnicodeLen = 0; |
699 | 0 | if (needed.value()) { |
700 | 0 | *unicodeData = reinterpret_cast<char16_t*>( |
701 | 0 | moz_xmalloc((needed.value() + 1) * sizeof(char16_t))); |
702 | 0 | uint32_t result; |
703 | 0 | size_t read; |
704 | 0 | size_t written; |
705 | 0 | bool hadErrors; |
706 | 0 | Tie(result, read, written, hadErrors) = |
707 | 0 | decoder->DecodeToUTF16(AsBytes(MakeSpan(data, dataLength)), |
708 | 0 | MakeSpan(*unicodeData, needed.value()), |
709 | 0 | true); |
710 | 0 | MOZ_ASSERT(result == kInputEmpty); |
711 | 0 | MOZ_ASSERT(read == size_t(dataLength)); |
712 | 0 | MOZ_ASSERT(written <= needed.value()); |
713 | 0 | Unused << hadErrors; |
714 | | #ifdef DEBUG_CLIPBOARD |
715 | | if (read != dataLength) |
716 | | printf("didn't consume all the bytes\n"); |
717 | | #endif |
718 | | outUnicodeLen = written; |
719 | 0 | // null terminate. |
720 | 0 | (*unicodeData)[outUnicodeLen] = '\0'; |
721 | 0 | } // if valid length |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | | /* |
726 | | * get "charset" information from clipboard data |
727 | | * return value can be: |
728 | | * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16" |
729 | | * 2. "UNKNOWN": mozilla can't detect what encode it use |
730 | | * 3. other: "text/html" with other charset than utf-16 |
731 | | */ |
732 | | void GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) |
733 | 0 | { |
734 | 0 | // if detect "FFFE" or "FEFF", assume UTF-16 |
735 | 0 | char16_t* beginChar = (char16_t*)data; |
736 | 0 | if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) { |
737 | 0 | str.AssignLiteral("UTF-16"); |
738 | 0 | return; |
739 | 0 | } |
740 | 0 | // no "FFFE" and "FEFF", assume ASCII first to find "charset" info |
741 | 0 | const nsDependentCString htmlStr(data, dataLength); |
742 | 0 | nsACString::const_iterator start, end; |
743 | 0 | htmlStr.BeginReading(start); |
744 | 0 | htmlStr.EndReading(end); |
745 | 0 | nsACString::const_iterator valueStart(start), valueEnd(start); |
746 | 0 |
|
747 | 0 | if (CaseInsensitiveFindInReadable( |
748 | 0 | NS_LITERAL_CSTRING("CONTENT=\"text/html;"), |
749 | 0 | start, end)) { |
750 | 0 | start = end; |
751 | 0 | htmlStr.EndReading(end); |
752 | 0 |
|
753 | 0 | if (CaseInsensitiveFindInReadable( |
754 | 0 | NS_LITERAL_CSTRING("charset="), |
755 | 0 | start, end)) { |
756 | 0 | valueStart = end; |
757 | 0 | start = end; |
758 | 0 | htmlStr.EndReading(end); |
759 | 0 |
|
760 | 0 | if (FindCharInReadable('"', start, end)) |
761 | 0 | valueEnd = start; |
762 | 0 | } |
763 | 0 | } |
764 | 0 | // find "charset" in HTML |
765 | 0 | if (valueStart != valueEnd) { |
766 | 0 | str = Substring(valueStart, valueEnd); |
767 | 0 | ToUpperCase(str); |
768 | | #ifdef DEBUG_CLIPBOARD |
769 | | printf("Charset of HTML = %s\n", charsetUpperStr.get()); |
770 | | #endif |
771 | | return; |
772 | 0 | } |
773 | 0 | str.AssignLiteral("UNKNOWN"); |
774 | 0 | } |