os_exchange_data_provider_aurax11.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
6
7#include "base/logging.h"
8#include "base/memory/ref_counted_memory.h"
9#include "base/message_loop/message_pump_x11.h"
10#include "base/strings/string_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "net/base/net_util.h"
13#include "ui/base/clipboard/clipboard.h"
14#include "ui/base/clipboard/scoped_clipboard_writer.h"
15#include "ui/base/x/selection_utils.h"
16#include "ui/base/x/x11_util.h"
17
18// Note: the GetBlah() methods are used immediately by the
19// web_contents_view_aura.cc:PrepareDropData(), while the omnibox is a
20// little more discriminating and calls HasBlah() before trying to get the
21// information.
22
23namespace ui {
24
25namespace {
26
27const char kDndSelection[] = "XdndSelection";
28
29const char* kAtomsToCache[] = {
30  kString,
31  kText,
32  kUtf8String,
33  kDndSelection,
34  Clipboard::kMimeTypeURIList,
35  kMimeTypeMozillaURL,
36  Clipboard::kMimeTypeText,
37  NULL
38};
39
40}  // namespace
41
42OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11(
43    ::Window x_window,
44    const SelectionFormatMap& selection)
45    : x_display_(gfx::GetXDisplay()),
46      x_root_window_(DefaultRootWindow(x_display_)),
47      own_window_(false),
48      x_window_(x_window),
49      atom_cache_(x_display_, kAtomsToCache),
50      format_map_(selection),
51      selection_owner_(x_display_, x_window_,
52                       atom_cache_.GetAtom(kDndSelection)) {
53  // We don't know all possible MIME types at compile time.
54  atom_cache_.allow_uncached_atoms();
55}
56
57OSExchangeDataProviderAuraX11::OSExchangeDataProviderAuraX11()
58    : x_display_(gfx::GetXDisplay()),
59      x_root_window_(DefaultRootWindow(x_display_)),
60      own_window_(true),
61      x_window_(XCreateWindow(
62          x_display_,
63          x_root_window_,
64          -100, -100, 10, 10,  // x, y, width, height
65          0,                   // border width
66          CopyFromParent,      // depth
67          InputOnly,
68          CopyFromParent,      // visual
69          0,
70          NULL)),
71      atom_cache_(x_display_, kAtomsToCache),
72      format_map_(),
73      selection_owner_(x_display_, x_window_,
74                       atom_cache_.GetAtom(kDndSelection)) {
75  // We don't know all possible MIME types at compile time.
76  atom_cache_.allow_uncached_atoms();
77
78  XStoreName(x_display_, x_window_, "Chromium Drag & Drop Window");
79
80  base::MessagePumpX11::Current()->AddDispatcherForWindow(this, x_window_);
81}
82
83OSExchangeDataProviderAuraX11::~OSExchangeDataProviderAuraX11() {
84  if (own_window_) {
85    base::MessagePumpX11::Current()->RemoveDispatcherForWindow(x_window_);
86    XDestroyWindow(x_display_, x_window_);
87  }
88}
89
90void OSExchangeDataProviderAuraX11::TakeOwnershipOfSelection() const {
91  selection_owner_.TakeOwnershipOfSelection(format_map_);
92}
93
94void OSExchangeDataProviderAuraX11::RetrieveTargets(
95    std::vector<Atom>* targets) const {
96  selection_owner_.RetrieveTargets(targets);
97}
98
99SelectionFormatMap OSExchangeDataProviderAuraX11::GetFormatMap() const {
100  // We return the |selection_owner_|'s format map instead of our own in case
101  // ours has been modified since TakeOwnershipOfSelection() was called.
102  return selection_owner_.selection_format_map();
103}
104
105OSExchangeData::Provider* OSExchangeDataProviderAuraX11::Clone() const {
106  OSExchangeDataProviderAuraX11* ret = new OSExchangeDataProviderAuraX11();
107  ret->format_map_ = format_map_;
108  return ret;
109}
110
111void OSExchangeDataProviderAuraX11::SetString(const base::string16& text_data) {
112  std::string utf8 = base::UTF16ToUTF8(text_data);
113  scoped_refptr<base::RefCountedMemory> mem(
114      base::RefCountedString::TakeString(&utf8));
115
116  format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeText), mem);
117  format_map_.Insert(atom_cache_.GetAtom(kText), mem);
118  format_map_.Insert(atom_cache_.GetAtom(kString), mem);
119  format_map_.Insert(atom_cache_.GetAtom(kUtf8String), mem);
120}
121
122void OSExchangeDataProviderAuraX11::SetURL(const GURL& url,
123                                           const base::string16& title) {
124  // Mozilla's URL format: (UTF16: URL, newline, title)
125  if (url.is_valid()) {
126    base::string16 spec = base::UTF8ToUTF16(url.spec());
127
128    std::vector<unsigned char> data;
129    ui::AddString16ToVector(spec, &data);
130    ui::AddString16ToVector(base::ASCIIToUTF16("\n"), &data);
131    ui::AddString16ToVector(title, &data);
132    scoped_refptr<base::RefCountedMemory> mem(
133        base::RefCountedBytes::TakeVector(&data));
134
135    format_map_.Insert(atom_cache_.GetAtom(kMimeTypeMozillaURL), mem);
136
137    SetString(spec);
138  }
139}
140
141void OSExchangeDataProviderAuraX11::SetFilename(const base::FilePath& path) {
142  std::vector<OSExchangeData::FileInfo> data;
143  data.push_back(OSExchangeData::FileInfo(path, base::FilePath()));
144  SetFilenames(data);
145}
146
147void OSExchangeDataProviderAuraX11::SetFilenames(
148    const std::vector<OSExchangeData::FileInfo>& filenames) {
149  std::vector<std::string> paths;
150  for (std::vector<OSExchangeData::FileInfo>::const_iterator it =
151           filenames.begin(); it != filenames.end(); ++it) {
152    std::string url_spec = net::FilePathToFileURL(it->path).spec();
153    if (!url_spec.empty())
154      paths.push_back(url_spec);
155  }
156
157  std::string joined_data = JoinString(paths, '\n');
158  scoped_refptr<base::RefCountedMemory> mem(
159      base::RefCountedString::TakeString(&joined_data));
160  format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeURIList), mem);
161}
162
163void OSExchangeDataProviderAuraX11::SetPickledData(
164    const OSExchangeData::CustomFormat& format,
165    const Pickle& pickle) {
166  const unsigned char* data =
167      reinterpret_cast<const unsigned char*>(pickle.data());
168
169  std::vector<unsigned char> bytes;
170  bytes.insert(bytes.end(), data, data + pickle.size());
171  scoped_refptr<base::RefCountedMemory> mem(
172      base::RefCountedBytes::TakeVector(&bytes));
173
174  format_map_.Insert(atom_cache_.GetAtom(format.ToString().c_str()), mem);
175}
176
177bool OSExchangeDataProviderAuraX11::GetString(base::string16* result) const {
178  if (HasFile()) {
179    // Various Linux file managers both pass a list of file:// URIs and set the
180    // string representation to the URI. We explicitly don't want to return use
181    // this representation.
182    return false;
183  }
184
185  std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
186  std::vector< ::Atom> requested_types;
187  ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
188
189  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
190  if (data.IsValid()) {
191    std::string text = data.GetText();
192    *result = base::UTF8ToUTF16(text);
193    return true;
194  }
195
196  return false;
197}
198
199bool OSExchangeDataProviderAuraX11::GetURLAndTitle(
200    OSExchangeData::FilenameToURLPolicy policy,
201    GURL* url,
202    base::string16* title) const {
203  std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
204  std::vector< ::Atom> requested_types;
205  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
206
207  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
208  if (data.IsValid()) {
209    // TODO(erg): Technically, both of these forms can accept multiple URLs,
210    // but that doesn't match the assumptions of the rest of the system which
211    // expect single types.
212
213    if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
214      // Mozilla URLs are (UTF16: URL, newline, title).
215      base::string16 unparsed;
216      data.AssignTo(&unparsed);
217
218      std::vector<base::string16> tokens;
219      size_t num_tokens = Tokenize(unparsed, base::ASCIIToUTF16("\n"), &tokens);
220      if (num_tokens > 0) {
221        if (num_tokens > 1)
222          *title = tokens[1];
223        else
224          *title = base::string16();
225
226        *url = GURL(tokens[0]);
227        return true;
228      }
229    } else if (data.GetType() == atom_cache_.GetAtom(
230                   Clipboard::kMimeTypeURIList)) {
231      std::vector<std::string> tokens = ui::ParseURIList(data);
232      for (std::vector<std::string>::const_iterator it = tokens.begin();
233           it != tokens.end(); ++it) {
234        GURL test_url(*it);
235        if (!test_url.SchemeIsFile() ||
236            policy == OSExchangeData::CONVERT_FILENAMES) {
237          *url = test_url;
238          *title = base::string16();
239          return true;
240        }
241      }
242    }
243  }
244
245  return false;
246}
247
248bool OSExchangeDataProviderAuraX11::GetFilename(base::FilePath* path) const {
249  std::vector<OSExchangeData::FileInfo> filenames;
250  if (GetFilenames(&filenames)) {
251    *path = filenames.front().path;
252    return true;
253  }
254
255  return false;
256}
257
258bool OSExchangeDataProviderAuraX11::GetFilenames(
259    std::vector<OSExchangeData::FileInfo>* filenames) const {
260  std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
261  std::vector< ::Atom> requested_types;
262  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
263
264  filenames->clear();
265  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
266  if (data.IsValid()) {
267    std::vector<std::string> tokens = ui::ParseURIList(data);
268    for (std::vector<std::string>::const_iterator it = tokens.begin();
269         it != tokens.end(); ++it) {
270      GURL url(*it);
271      base::FilePath file_path;
272      if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path)) {
273        filenames->push_back(OSExchangeData::FileInfo(file_path,
274                                                      base::FilePath()));
275      }
276    }
277  }
278
279  return !filenames->empty();
280}
281
282bool OSExchangeDataProviderAuraX11::GetPickledData(
283    const OSExchangeData::CustomFormat& format,
284    Pickle* pickle) const {
285  std::vector< ::Atom> requested_types;
286  requested_types.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
287
288  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
289  if (data.IsValid()) {
290    // Note that the pickle object on the right hand side of the assignment
291    // only refers to the bytes in |data|. The assignment copies the data.
292    *pickle = Pickle(reinterpret_cast<const char*>(data.GetData()),
293                     static_cast<int>(data.GetSize()));
294    return true;
295  }
296
297  return false;
298}
299
300bool OSExchangeDataProviderAuraX11::HasString() const {
301  std::vector< ::Atom> text_atoms = ui::GetTextAtomsFrom(&atom_cache_);
302  std::vector< ::Atom> requested_types;
303  ui::GetAtomIntersection(text_atoms, GetTargets(), &requested_types);
304  return !requested_types.empty() && !HasFile();
305}
306
307bool OSExchangeDataProviderAuraX11::HasURL(
308    OSExchangeData::FilenameToURLPolicy policy) const {
309  std::vector< ::Atom> url_atoms = ui::GetURLAtomsFrom(&atom_cache_);
310  std::vector< ::Atom> requested_types;
311  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
312
313  if (requested_types.empty())
314    return false;
315
316  // The Linux desktop doesn't differentiate between files and URLs like
317  // Windows does and stuffs all the data into one mime type.
318  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
319  if (data.IsValid()) {
320    if (data.GetType() == atom_cache_.GetAtom(kMimeTypeMozillaURL)) {
321      // File managers shouldn't be using this type, so this is a URL.
322      return true;
323    } else if (data.GetType() == atom_cache_.GetAtom(
324        ui::Clipboard::kMimeTypeURIList)) {
325      std::vector<std::string> tokens = ui::ParseURIList(data);
326      for (std::vector<std::string>::const_iterator it = tokens.begin();
327           it != tokens.end(); ++it) {
328        if (!GURL(*it).SchemeIsFile() ||
329            policy == OSExchangeData::CONVERT_FILENAMES)
330          return true;
331      }
332
333      return false;
334    }
335  }
336
337  return false;
338}
339
340bool OSExchangeDataProviderAuraX11::HasFile() const {
341  std::vector< ::Atom> url_atoms = ui::GetURIListAtomsFrom(&atom_cache_);
342  std::vector< ::Atom> requested_types;
343  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
344
345  if (requested_types.empty())
346    return false;
347
348  // To actually answer whether we have a file, we need to look through the
349  // contents of the kMimeTypeURIList type, and see if any of them are file://
350  // URIs.
351  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
352  if (data.IsValid()) {
353    std::vector<std::string> tokens = ui::ParseURIList(data);
354    for (std::vector<std::string>::const_iterator it = tokens.begin();
355         it != tokens.end(); ++it) {
356      GURL url(*it);
357      base::FilePath file_path;
358      if (url.SchemeIsFile() && net::FileURLToFilePath(url, &file_path))
359        return true;
360    }
361  }
362
363  return false;
364}
365
366bool OSExchangeDataProviderAuraX11::HasCustomFormat(
367    const OSExchangeData::CustomFormat& format) const {
368  std::vector< ::Atom> url_atoms;
369  url_atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
370  std::vector< ::Atom> requested_types;
371  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
372
373  return !requested_types.empty();
374}
375
376void OSExchangeDataProviderAuraX11::SetHtml(const base::string16& html,
377                                            const GURL& base_url) {
378  std::vector<unsigned char> bytes;
379  // Manually jam a UTF16 BOM into bytes because otherwise, other programs will
380  // assume UTF-8.
381  bytes.push_back(0xFF);
382  bytes.push_back(0xFE);
383  ui::AddString16ToVector(html, &bytes);
384  scoped_refptr<base::RefCountedMemory> mem(
385      base::RefCountedBytes::TakeVector(&bytes));
386
387  format_map_.Insert(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML), mem);
388}
389
390bool OSExchangeDataProviderAuraX11::GetHtml(base::string16* html,
391                                            GURL* base_url) const {
392  std::vector< ::Atom> url_atoms;
393  url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
394  std::vector< ::Atom> requested_types;
395  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
396
397  ui::SelectionData data(format_map_.GetFirstOf(requested_types));
398  if (data.IsValid()) {
399    *html = data.GetHtml();
400    *base_url = GURL();
401    return true;
402  }
403
404  return false;
405}
406
407bool OSExchangeDataProviderAuraX11::HasHtml() const {
408  std::vector< ::Atom> url_atoms;
409  url_atoms.push_back(atom_cache_.GetAtom(Clipboard::kMimeTypeHTML));
410  std::vector< ::Atom> requested_types;
411  ui::GetAtomIntersection(url_atoms, GetTargets(), &requested_types);
412
413  return !requested_types.empty();
414}
415
416void OSExchangeDataProviderAuraX11::SetDragImage(
417    const gfx::ImageSkia& image,
418    const gfx::Vector2d& cursor_offset) {
419  drag_image_ = image;
420  drag_image_offset_ = cursor_offset;
421}
422
423const gfx::ImageSkia& OSExchangeDataProviderAuraX11::GetDragImage() const {
424  return drag_image_;
425}
426
427const gfx::Vector2d& OSExchangeDataProviderAuraX11::GetDragImageOffset() const {
428  return drag_image_offset_;
429}
430
431uint32_t OSExchangeDataProviderAuraX11::Dispatch(
432    const base::NativeEvent& event) {
433  XEvent* xev = event;
434  switch (xev->type) {
435    case SelectionRequest:
436      selection_owner_.OnSelectionRequest(xev->xselectionrequest);
437      break;
438    default:
439      NOTIMPLEMENTED();
440  }
441
442  return POST_DISPATCH_NONE;
443}
444
445bool OSExchangeDataProviderAuraX11::GetPlainTextURL(GURL* url) const {
446  base::string16 text;
447  if (GetString(&text)) {
448    GURL test_url(text);
449    if (test_url.is_valid()) {
450      *url = test_url;
451      return true;
452    }
453  }
454
455  return false;
456}
457
458std::vector< ::Atom> OSExchangeDataProviderAuraX11::GetTargets() const {
459  return format_map_.GetTypes();
460}
461
462///////////////////////////////////////////////////////////////////////////////
463// OSExchangeData, public:
464
465// static
466OSExchangeData::Provider* OSExchangeData::CreateProvider() {
467  return new OSExchangeDataProviderAuraX11();
468}
469
470}  // namespace ui
471