1/* 2 * Copyright (C) 2010 Martin Robinson <mrobinson@webkit.org> 3 * Copyright (C) Igalia S.L. 4 * All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 * 21 */ 22#include "config.h" 23#include "PasteboardHelper.h" 24 25#include "Chrome.h" 26#include "DataObjectGtk.h" 27#include "Frame.h" 28#include "GtkVersioning.h" 29#include "Page.h" 30#include "Pasteboard.h" 31#include "TextResourceDecoder.h" 32#include <gtk/gtk.h> 33#include <wtf/gobject/GOwnPtr.h> 34 35namespace WebCore { 36 37static GdkAtom textPlainAtom; 38static GdkAtom markupAtom; 39static GdkAtom netscapeURLAtom; 40static GdkAtom uriListAtom; 41static GdkAtom smartPasteAtom; 42static String gMarkupPrefix; 43 44static void removeMarkupPrefix(String& markup) 45{ 46 // The markup prefix is not harmful, but we remove it from the string anyway, so that 47 // we can have consistent results with other ports during the layout tests. 48 if (markup.startsWith(gMarkupPrefix)) 49 markup.remove(0, gMarkupPrefix.length()); 50} 51 52static void initGdkAtoms() 53{ 54 static gboolean initialized = FALSE; 55 56 if (initialized) 57 return; 58 59 initialized = TRUE; 60 61 textPlainAtom = gdk_atom_intern("text/plain;charset=utf-8", FALSE); 62 markupAtom = gdk_atom_intern("text/html", FALSE); 63 netscapeURLAtom = gdk_atom_intern("_NETSCAPE_URL", FALSE); 64 uriListAtom = gdk_atom_intern("text/uri-list", FALSE); 65 smartPasteAtom = gdk_atom_intern("application/vnd.webkitgtk.smartpaste", FALSE); 66 gMarkupPrefix = "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"; 67} 68 69PasteboardHelper* PasteboardHelper::defaultPasteboardHelper() 70{ 71 DEFINE_STATIC_LOCAL(PasteboardHelper, defaultHelper, ()); 72 return &defaultHelper; 73} 74 75PasteboardHelper::PasteboardHelper() 76 : m_targetList(gtk_target_list_new(0, 0)) 77 , m_usePrimarySelectionClipboard(false) 78{ 79 initGdkAtoms(); 80 81 gtk_target_list_add_text_targets(m_targetList, PasteboardHelper::TargetTypeText); 82 gtk_target_list_add(m_targetList, markupAtom, 0, PasteboardHelper::TargetTypeMarkup); 83 gtk_target_list_add_uri_targets(m_targetList, PasteboardHelper::TargetTypeURIList); 84 gtk_target_list_add(m_targetList, netscapeURLAtom, 0, PasteboardHelper::TargetTypeNetscapeURL); 85 gtk_target_list_add_image_targets(m_targetList, PasteboardHelper::TargetTypeImage, TRUE); 86} 87 88PasteboardHelper::~PasteboardHelper() 89{ 90 gtk_target_list_unref(m_targetList); 91} 92 93static inline GdkDisplay* displayFromFrame(Frame* frame) 94{ 95 ASSERT(frame); 96 Page* page = frame->page(); 97 ASSERT(page); 98 PlatformPageClient client = page->chrome().platformPageClient(); 99 return client ? gtk_widget_get_display(client) : gdk_display_get_default(); 100} 101 102GtkClipboard* PasteboardHelper::getCurrentClipboard(Frame* frame) 103{ 104 if (m_usePrimarySelectionClipboard) 105 return getPrimarySelectionClipboard(frame); 106 return getClipboard(frame); 107} 108 109GtkClipboard* PasteboardHelper::getClipboard(Frame* frame) const 110{ 111 return gtk_clipboard_get_for_display(displayFromFrame(frame), GDK_SELECTION_CLIPBOARD); 112} 113 114GtkClipboard* PasteboardHelper::getPrimarySelectionClipboard(Frame* frame) const 115{ 116 return gtk_clipboard_get_for_display(displayFromFrame(frame), GDK_SELECTION_PRIMARY); 117} 118 119GtkTargetList* PasteboardHelper::targetList() const 120{ 121 return m_targetList; 122} 123 124static String selectionDataToUTF8String(GtkSelectionData* data) 125{ 126 // g_strndup guards against selection data that is not null-terminated. 127 GOwnPtr<gchar> markupString(g_strndup(reinterpret_cast<const char*>(gtk_selection_data_get_data(data)), gtk_selection_data_get_length(data))); 128 return String::fromUTF8(markupString.get()); 129} 130 131void PasteboardHelper::getClipboardContents(GtkClipboard* clipboard) 132{ 133 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 134 ASSERT(dataObject); 135 136 if (gtk_clipboard_wait_is_text_available(clipboard)) { 137 GOwnPtr<gchar> textData(gtk_clipboard_wait_for_text(clipboard)); 138 if (textData) 139 dataObject->setText(String::fromUTF8(textData.get())); 140 } 141 142 if (gtk_clipboard_wait_is_target_available(clipboard, markupAtom)) { 143 if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, markupAtom)) { 144 String markup(selectionDataToUTF8String(data)); 145 removeMarkupPrefix(markup); 146 dataObject->setMarkup(markup); 147 gtk_selection_data_free(data); 148 } 149 } 150 151 if (gtk_clipboard_wait_is_target_available(clipboard, uriListAtom)) { 152 if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, uriListAtom)) { 153 dataObject->setURIList(selectionDataToUTF8String(data)); 154 gtk_selection_data_free(data); 155 } 156 } 157} 158 159void PasteboardHelper::fillSelectionData(GtkSelectionData* selectionData, guint info, DataObjectGtk* dataObject) 160{ 161 if (info == TargetTypeText) 162 gtk_selection_data_set_text(selectionData, dataObject->text().utf8().data(), -1); 163 164 else if (info == TargetTypeMarkup) { 165 // Some Linux applications refuse to accept pasted markup unless it is 166 // prefixed by a content-type meta tag. 167 CString markup = String(gMarkupPrefix + dataObject->markup()).utf8(); 168 gtk_selection_data_set(selectionData, markupAtom, 8, 169 reinterpret_cast<const guchar*>(markup.data()), markup.length()); 170 171 } else if (info == TargetTypeURIList) { 172 CString uriList = dataObject->uriList().utf8(); 173 gtk_selection_data_set(selectionData, uriListAtom, 8, 174 reinterpret_cast<const guchar*>(uriList.data()), uriList.length()); 175 176 } else if (info == TargetTypeNetscapeURL && dataObject->hasURL()) { 177 String url(dataObject->url()); 178 String result(url); 179 result.append("\n"); 180 181 if (dataObject->hasText()) 182 result.append(dataObject->text()); 183 else 184 result.append(url); 185 186 GOwnPtr<gchar> resultData(g_strdup(result.utf8().data())); 187 gtk_selection_data_set(selectionData, netscapeURLAtom, 8, 188 reinterpret_cast<const guchar*>(resultData.get()), strlen(resultData.get())); 189 190 } else if (info == TargetTypeImage) 191 gtk_selection_data_set_pixbuf(selectionData, dataObject->image()); 192 193 else if (info == TargetTypeSmartPaste) 194 gtk_selection_data_set_text(selectionData, "", -1); 195} 196 197GtkTargetList* PasteboardHelper::targetListForDataObject(DataObjectGtk* dataObject, SmartPasteInclusion shouldInludeSmartPaste) 198{ 199 GtkTargetList* list = gtk_target_list_new(0, 0); 200 201 if (dataObject->hasText()) 202 gtk_target_list_add_text_targets(list, TargetTypeText); 203 204 if (dataObject->hasMarkup()) 205 gtk_target_list_add(list, markupAtom, 0, TargetTypeMarkup); 206 207 if (dataObject->hasURIList()) { 208 gtk_target_list_add_uri_targets(list, TargetTypeURIList); 209 gtk_target_list_add(list, netscapeURLAtom, 0, TargetTypeNetscapeURL); 210 } 211 212 if (dataObject->hasImage()) 213 gtk_target_list_add_image_targets(list, TargetTypeImage, TRUE); 214 215 if (shouldInludeSmartPaste == IncludeSmartPaste) 216 gtk_target_list_add(list, smartPasteAtom, 0, TargetTypeSmartPaste); 217 218 return list; 219} 220 221void PasteboardHelper::fillDataObjectFromDropData(GtkSelectionData* data, guint info, DataObjectGtk* dataObject) 222{ 223 if (!gtk_selection_data_get_data(data)) 224 return; 225 226 GdkAtom target = gtk_selection_data_get_target(data); 227 if (target == textPlainAtom) 228 dataObject->setText(selectionDataToUTF8String(data)); 229 else if (target == markupAtom) { 230 String markup(selectionDataToUTF8String(data)); 231 removeMarkupPrefix(markup); 232 dataObject->setMarkup(markup); 233 } else if (target == uriListAtom) { 234 dataObject->setURIList(selectionDataToUTF8String(data)); 235 } else if (target == netscapeURLAtom) { 236 String urlWithLabel(selectionDataToUTF8String(data)); 237 Vector<String> pieces; 238 urlWithLabel.split("\n", pieces); 239 240 // Give preference to text/uri-list here, as it can hold more 241 // than one URI but still take the label if there is one. 242 if (!dataObject->hasURIList()) 243 dataObject->setURIList(pieces[0]); 244 if (pieces.size() > 1) 245 dataObject->setText(pieces[1]); 246 } 247} 248 249Vector<GdkAtom> PasteboardHelper::dropAtomsForContext(GtkWidget* widget, GdkDragContext* context) 250{ 251 // Always search for these common atoms. 252 Vector<GdkAtom> dropAtoms; 253 dropAtoms.append(textPlainAtom); 254 dropAtoms.append(markupAtom); 255 dropAtoms.append(uriListAtom); 256 dropAtoms.append(netscapeURLAtom); 257 258 // For images, try to find the most applicable image type. 259 GRefPtr<GtkTargetList> list = adoptGRef(gtk_target_list_new(0, 0)); 260 gtk_target_list_add_image_targets(list.get(), TargetTypeImage, TRUE); 261 GdkAtom atom = gtk_drag_dest_find_target(widget, context, list.get()); 262 if (atom != GDK_NONE) 263 dropAtoms.append(atom); 264 265 return dropAtoms; 266} 267 268static DataObjectGtk* settingClipboardDataObject = 0; 269 270static void getClipboardContentsCallback(GtkClipboard* clipboard, GtkSelectionData *selectionData, guint info, gpointer data) 271{ 272 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 273 ASSERT(dataObject); 274 PasteboardHelper::defaultPasteboardHelper()->fillSelectionData(selectionData, info, dataObject); 275} 276 277static void clearClipboardContentsCallback(GtkClipboard* clipboard, gpointer data) 278{ 279 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 280 ASSERT(dataObject); 281 282 // Only clear the DataObject for this clipboard if we are not currently setting it. 283 if (dataObject != settingClipboardDataObject) 284 dataObject->clearAll(); 285 286 if (!data) 287 return; 288 289 GClosure* callback = static_cast<GClosure*>(data); 290 GValue firstArgument = {0, {{0}}}; 291 g_value_init(&firstArgument, G_TYPE_POINTER); 292 g_value_set_pointer(&firstArgument, clipboard); 293 g_closure_invoke(callback, 0, 1, &firstArgument, 0); 294 g_closure_unref(callback); 295} 296 297void PasteboardHelper::writeClipboardContents(GtkClipboard* clipboard, SmartPasteInclusion includeSmartPaste, GClosure* callback) 298{ 299 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 300 GtkTargetList* list = targetListForDataObject(dataObject, includeSmartPaste); 301 302 int numberOfTargets; 303 GtkTargetEntry* table = gtk_target_table_new_from_list(list, &numberOfTargets); 304 305 if (numberOfTargets > 0 && table) { 306 settingClipboardDataObject = dataObject; 307 308 gtk_clipboard_set_with_data(clipboard, table, numberOfTargets, 309 getClipboardContentsCallback, clearClipboardContentsCallback, callback); 310 gtk_clipboard_set_can_store(clipboard, 0, 0); 311 312 settingClipboardDataObject = 0; 313 314 } else 315 gtk_clipboard_clear(clipboard); 316 317 if (table) 318 gtk_target_table_free(table, numberOfTargets); 319 gtk_target_list_unref(list); 320} 321 322bool PasteboardHelper::clipboardContentSupportsSmartReplace(GtkClipboard* clipboard) 323{ 324 return gtk_clipboard_wait_is_target_available(clipboard, smartPasteAtom); 325} 326 327} 328 329