1 /* 2 * Copyright (C) 2010 Martin Robinson <mrobinson (at) 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 35 namespace WebCore { 36 37 static GdkAtom textPlainAtom; 38 static GdkAtom markupAtom; 39 static GdkAtom netscapeURLAtom; 40 static GdkAtom uriListAtom; 41 static String gMarkupPrefix; 42 43 static void removeMarkupPrefix(String& markup) 44 { 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 52 static 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 gMarkupPrefix = "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"; 66 } 67 68 PasteboardHelper::PasteboardHelper() 69 : m_targetList(gtk_target_list_new(0, 0)) 70 { 71 initGdkAtoms(); 72 } 73 74 PasteboardHelper::~PasteboardHelper() 75 { 76 gtk_target_list_unref(m_targetList); 77 } 78 79 void PasteboardHelper::initializeTargetList() 80 { 81 gtk_target_list_add_text_targets(m_targetList, getIdForTargetType(TargetTypeText)); 82 gtk_target_list_add(m_targetList, markupAtom, 0, getIdForTargetType(TargetTypeMarkup)); 83 gtk_target_list_add_uri_targets(m_targetList, getIdForTargetType(TargetTypeURIList)); 84 gtk_target_list_add(m_targetList, netscapeURLAtom, 0, getIdForTargetType(TargetTypeNetscapeURL)); 85 gtk_target_list_add_image_targets(m_targetList, getIdForTargetType(TargetTypeImage), TRUE); 86 } 87 88 static inline GtkWidget* widgetFromFrame(Frame* frame) 89 { 90 ASSERT(frame); 91 Page* page = frame->page(); 92 ASSERT(page); 93 Chrome* chrome = page->chrome(); 94 ASSERT(chrome); 95 PlatformPageClient client = chrome->platformPageClient(); 96 ASSERT(client); 97 return client; 98 } 99 100 GtkClipboard* PasteboardHelper::getCurrentClipboard(Frame* frame) 101 { 102 if (usePrimarySelectionClipboard(widgetFromFrame(frame))) 103 return getPrimarySelectionClipboard(frame); 104 return getClipboard(frame); 105 } 106 107 GtkClipboard* PasteboardHelper::getClipboard(Frame* frame) const 108 { 109 return gtk_widget_get_clipboard(widgetFromFrame(frame), GDK_SELECTION_CLIPBOARD); 110 } 111 112 GtkClipboard* PasteboardHelper::getPrimarySelectionClipboard(Frame* frame) const 113 { 114 return gtk_widget_get_clipboard(widgetFromFrame(frame), GDK_SELECTION_PRIMARY); 115 } 116 117 GtkTargetList* PasteboardHelper::targetList() const 118 { 119 return m_targetList; 120 } 121 122 static String selectionDataToUTF8String(GtkSelectionData* data) 123 { 124 // g_strndup guards against selection data that is not null-terminated. 125 GOwnPtr<gchar> markupString(g_strndup(reinterpret_cast<const char*>(gtk_selection_data_get_data(data)), gtk_selection_data_get_length(data))); 126 return String::fromUTF8(markupString.get()); 127 } 128 129 void PasteboardHelper::getClipboardContents(GtkClipboard* clipboard) 130 { 131 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 132 ASSERT(dataObject); 133 134 if (gtk_clipboard_wait_is_text_available(clipboard)) { 135 GOwnPtr<gchar> textData(gtk_clipboard_wait_for_text(clipboard)); 136 if (textData) 137 dataObject->setText(String::fromUTF8(textData.get())); 138 } 139 140 if (gtk_clipboard_wait_is_target_available(clipboard, markupAtom)) { 141 if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, markupAtom)) { 142 String markup(selectionDataToUTF8String(data)); 143 removeMarkupPrefix(markup); 144 dataObject->setMarkup(markup); 145 gtk_selection_data_free(data); 146 } 147 } 148 149 if (gtk_clipboard_wait_is_target_available(clipboard, uriListAtom)) { 150 if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, uriListAtom)) { 151 dataObject->setURIList(selectionDataToUTF8String(data)); 152 gtk_selection_data_free(data); 153 } 154 } 155 } 156 157 void PasteboardHelper::fillSelectionData(GtkSelectionData* selectionData, guint info, DataObjectGtk* dataObject) 158 { 159 if (info == getIdForTargetType(TargetTypeText)) 160 gtk_selection_data_set_text(selectionData, dataObject->text().utf8().data(), -1); 161 162 else if (info == getIdForTargetType(TargetTypeMarkup)) { 163 // Some Linux applications refuse to accept pasted markup unless it is 164 // prefixed by a content-type meta tag. 165 CString markup = (gMarkupPrefix + dataObject->markup()).utf8(); 166 gtk_selection_data_set(selectionData, markupAtom, 8, 167 reinterpret_cast<const guchar*>(markup.data()), markup.length() + 1); 168 169 } else if (info == getIdForTargetType(TargetTypeURIList)) { 170 CString uriList = dataObject->uriList().utf8(); 171 gtk_selection_data_set(selectionData, uriListAtom, 8, 172 reinterpret_cast<const guchar*>(uriList.data()), uriList.length() + 1); 173 174 } else if (info == getIdForTargetType(TargetTypeNetscapeURL) && dataObject->hasURL()) { 175 String url(dataObject->url()); 176 String result(url); 177 result.append("\n"); 178 179 if (dataObject->hasText()) 180 result.append(dataObject->text()); 181 else 182 result.append(url); 183 184 GOwnPtr<gchar> resultData(g_strdup(result.utf8().data())); 185 gtk_selection_data_set(selectionData, netscapeURLAtom, 8, 186 reinterpret_cast<const guchar*>(resultData.get()), strlen(resultData.get()) + 1); 187 188 } else if (info == getIdForTargetType(TargetTypeImage)) 189 gtk_selection_data_set_pixbuf(selectionData, dataObject->image()); 190 } 191 192 GtkTargetList* PasteboardHelper::targetListForDataObject(DataObjectGtk* dataObject) 193 { 194 GtkTargetList* list = gtk_target_list_new(0, 0); 195 196 if (dataObject->hasText()) 197 gtk_target_list_add_text_targets(list, getIdForTargetType(TargetTypeText)); 198 199 if (dataObject->hasMarkup()) 200 gtk_target_list_add(list, markupAtom, 0, getIdForTargetType(TargetTypeMarkup)); 201 202 if (dataObject->hasURIList()) { 203 gtk_target_list_add_uri_targets(list, getIdForTargetType(TargetTypeURIList)); 204 gtk_target_list_add(list, netscapeURLAtom, 0, getIdForTargetType(TargetTypeNetscapeURL)); 205 } 206 207 if (dataObject->hasImage()) 208 gtk_target_list_add_image_targets(list, getIdForTargetType(TargetTypeImage), TRUE); 209 210 return list; 211 } 212 213 void PasteboardHelper::fillDataObjectFromDropData(GtkSelectionData* data, guint info, DataObjectGtk* dataObject) 214 { 215 if (!gtk_selection_data_get_data(data)) 216 return; 217 218 GdkAtom target = gtk_selection_data_get_target(data); 219 if (target == textPlainAtom) 220 dataObject->setText(selectionDataToUTF8String(data)); 221 else if (target == markupAtom) { 222 String markup(selectionDataToUTF8String(data)); 223 removeMarkupPrefix(markup); 224 dataObject->setMarkup(markup); 225 } else if (target == uriListAtom) { 226 dataObject->setURIList(selectionDataToUTF8String(data)); 227 } else if (target == netscapeURLAtom) { 228 String urlWithLabel(selectionDataToUTF8String(data)); 229 Vector<String> pieces; 230 urlWithLabel.split("\n", pieces); 231 232 // Give preference to text/uri-list here, as it can hold more 233 // than one URI but still take the label if there is one. 234 if (!dataObject->hasURIList()) 235 dataObject->setURIList(pieces[0]); 236 if (pieces.size() > 1) 237 dataObject->setText(pieces[1]); 238 } 239 } 240 241 Vector<GdkAtom> PasteboardHelper::dropAtomsForContext(GtkWidget* widget, GdkDragContext* context) 242 { 243 // Always search for these common atoms. 244 Vector<GdkAtom> dropAtoms; 245 dropAtoms.append(textPlainAtom); 246 dropAtoms.append(markupAtom); 247 dropAtoms.append(uriListAtom); 248 dropAtoms.append(netscapeURLAtom); 249 250 // For images, try to find the most applicable image type. 251 GRefPtr<GtkTargetList> list(gtk_target_list_new(0, 0)); 252 gtk_target_list_add_image_targets(list.get(), getIdForTargetType(TargetTypeImage), TRUE); 253 GdkAtom atom = gtk_drag_dest_find_target(widget, context, list.get()); 254 if (atom != GDK_NONE) 255 dropAtoms.append(atom); 256 257 return dropAtoms; 258 } 259 260 static DataObjectGtk* settingClipboardDataObject = 0; 261 262 static void getClipboardContentsCallback(GtkClipboard* clipboard, GtkSelectionData *selectionData, guint info, gpointer data) 263 { 264 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 265 ASSERT(dataObject); 266 Pasteboard::generalPasteboard()->helper()->fillSelectionData(selectionData, info, dataObject); 267 } 268 269 static void clearClipboardContentsCallback(GtkClipboard* clipboard, gpointer data) 270 { 271 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 272 ASSERT(dataObject); 273 274 // Only clear the DataObject for this clipboard if we are not currently setting it. 275 if (dataObject != settingClipboardDataObject) 276 dataObject->clear(); 277 278 if (!data) 279 return; 280 281 GClosure* callback = static_cast<GClosure*>(data); 282 GValue firstArgument = {0, {{0}}}; 283 g_value_init(&firstArgument, G_TYPE_POINTER); 284 g_value_set_pointer(&firstArgument, clipboard); 285 g_closure_invoke(callback, 0, 1, &firstArgument, 0); 286 g_closure_unref(callback); 287 } 288 289 void PasteboardHelper::writeClipboardContents(GtkClipboard* clipboard, GClosure* callback) 290 { 291 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 292 GtkTargetList* list = targetListForDataObject(dataObject); 293 294 int numberOfTargets; 295 GtkTargetEntry* table = gtk_target_table_new_from_list(list, &numberOfTargets); 296 297 if (numberOfTargets > 0 && table) { 298 settingClipboardDataObject = dataObject; 299 300 gtk_clipboard_set_with_data(clipboard, table, numberOfTargets, 301 getClipboardContentsCallback, clearClipboardContentsCallback, callback); 302 gtk_clipboard_set_can_store(clipboard, 0, 0); 303 304 settingClipboardDataObject = 0; 305 306 } else 307 gtk_clipboard_clear(clipboard); 308 309 if (table) 310 gtk_target_table_free(table, numberOfTargets); 311 gtk_target_list_unref(list); 312 } 313 314 } 315 316