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
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
removeMarkupPrefix(String & markup)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
initGdkAtoms()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
PasteboardHelper()68 PasteboardHelper::PasteboardHelper()
69 : m_targetList(gtk_target_list_new(0, 0))
70 {
71 initGdkAtoms();
72 }
73
~PasteboardHelper()74 PasteboardHelper::~PasteboardHelper()
75 {
76 gtk_target_list_unref(m_targetList);
77 }
78
initializeTargetList()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
widgetFromFrame(Frame * frame)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
getCurrentClipboard(Frame * frame)100 GtkClipboard* PasteboardHelper::getCurrentClipboard(Frame* frame)
101 {
102 if (usePrimarySelectionClipboard(widgetFromFrame(frame)))
103 return getPrimarySelectionClipboard(frame);
104 return getClipboard(frame);
105 }
106
getClipboard(Frame * frame) const107 GtkClipboard* PasteboardHelper::getClipboard(Frame* frame) const
108 {
109 return gtk_widget_get_clipboard(widgetFromFrame(frame), GDK_SELECTION_CLIPBOARD);
110 }
111
getPrimarySelectionClipboard(Frame * frame) const112 GtkClipboard* PasteboardHelper::getPrimarySelectionClipboard(Frame* frame) const
113 {
114 return gtk_widget_get_clipboard(widgetFromFrame(frame), GDK_SELECTION_PRIMARY);
115 }
116
targetList() const117 GtkTargetList* PasteboardHelper::targetList() const
118 {
119 return m_targetList;
120 }
121
selectionDataToUTF8String(GtkSelectionData * data)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
getClipboardContents(GtkClipboard * clipboard)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
fillSelectionData(GtkSelectionData * selectionData,guint info,DataObjectGtk * dataObject)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
targetListForDataObject(DataObjectGtk * dataObject)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
fillDataObjectFromDropData(GtkSelectionData * data,guint info,DataObjectGtk * dataObject)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
dropAtomsForContext(GtkWidget * widget,GdkDragContext * context)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
getClipboardContentsCallback(GtkClipboard * clipboard,GtkSelectionData * selectionData,guint info,gpointer data)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
clearClipboardContentsCallback(GtkClipboard * clipboard,gpointer data)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
writeClipboardContents(GtkClipboard * clipboard,GClosure * callback)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