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