widget/gtk/nsClipboard.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial