clipboard_aurax11.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/clipboard/clipboard.h"
6
7#include <X11/extensions/Xfixes.h>
8#include <X11/Xatom.h>
9#include <list>
10#include <set>
11
12#include "base/basictypes.h"
13#include "base/files/file_path.h"
14#include "base/i18n/icu_string_conversions.h"
15#include "base/logging.h"
16#include "base/memory/scoped_ptr.h"
17#include "base/memory/singleton.h"
18#include "base/message_pump_aurax11.h"
19#include "base/message_pump_observer.h"
20#include "base/run_loop.h"
21#include "base/stl_util.h"
22#include "base/utf_string_conversions.h"
23#include "third_party/skia/include/core/SkBitmap.h"
24#include "ui/base/clipboard/custom_data_helper.h"
25#include "ui/base/x/x11_atom_cache.h"
26#include "ui/base/x/x11_util.h"
27
28#include "ui/gfx/size.h"
29
30namespace ui {
31
32namespace {
33
34const char kChromeSelection[] = "CHROME_SELECTION";
35const char kClipboard[] = "CLIPBOARD";
36const char kMimeTypeBitmap[] = "image/bmp";
37const char kMimeTypeFilename[] = "chromium/filename";
38const char kMimeTypeMozillaURL[] = "text/x-moz-url";
39const char kMimeTypePepperCustomData[] = "chromium/x-pepper-custom-data";
40const char kMimeTypeWebkitSmartPaste[] = "chromium/x-webkit-paste";
41const char kMultiple[] = "MULTIPLE";
42const char kSourceTagType[] = "org.chromium.source-tag";
43const char kString[] = "STRING";
44const char kTargets[] = "TARGETS";
45const char kText[] = "TEXT";
46const char kUtf8String[] = "UTF8_STRING";
47
48const char* kAtomsToCache[] = {
49  kChromeSelection,
50  kClipboard,
51  kMimeTypeBitmap,
52  kMimeTypeFilename,
53  kMimeTypeMozillaURL,
54  kMimeTypeWebkitSmartPaste,
55  kMultiple,
56  kSourceTagType,
57  kString,
58  kTargets,
59  kText,
60  kUtf8String,
61  NULL
62};
63
64///////////////////////////////////////////////////////////////////////////////
65
66// Returns a list of all text atoms that we handle.
67std::vector< ::Atom> GetTextAtomsFrom(const X11AtomCache* atom_cache) {
68  std::vector< ::Atom> atoms;
69  atoms.push_back(atom_cache->GetAtom(kUtf8String));
70  atoms.push_back(atom_cache->GetAtom(kString));
71  atoms.push_back(atom_cache->GetAtom(kText));
72  return atoms;
73}
74
75///////////////////////////////////////////////////////////////////////////////
76
77// Uses the XFixes API to provide sequence numbers for GetSequenceNumber().
78class SelectionChangeObserver : public base::MessagePumpObserver {
79 public:
80  static SelectionChangeObserver* GetInstance();
81
82  uint64 clipboard_sequence_number() const {
83    return clipboard_sequence_number_;
84  }
85  uint64 primary_sequence_number() const { return primary_sequence_number_; }
86
87 private:
88  friend struct DefaultSingletonTraits<SelectionChangeObserver>;
89
90  SelectionChangeObserver();
91  ~SelectionChangeObserver();
92
93  // Overridden from base::MessagePumpObserver:
94  virtual base::EventStatus WillProcessEvent(
95      const base::NativeEvent& event) OVERRIDE;
96  virtual void DidProcessEvent(
97      const base::NativeEvent& event) OVERRIDE {}
98
99  int event_base_;
100  Atom clipboard_atom_;
101  uint64 clipboard_sequence_number_;
102  uint64 primary_sequence_number_;
103
104  DISALLOW_COPY_AND_ASSIGN(SelectionChangeObserver);
105};
106
107SelectionChangeObserver::SelectionChangeObserver()
108    : event_base_(-1),
109      clipboard_atom_(None),
110      clipboard_sequence_number_(0),
111      primary_sequence_number_(0) {
112  int ignored;
113  if (XFixesQueryExtension(GetXDisplay(), &event_base_, &ignored)) {
114    clipboard_atom_ = XInternAtom(GetXDisplay(), kClipboard, false);
115    XFixesSelectSelectionInput(GetXDisplay(), GetX11RootWindow(),
116                               clipboard_atom_,
117                               XFixesSetSelectionOwnerNotifyMask |
118                               XFixesSelectionWindowDestroyNotifyMask |
119                               XFixesSelectionClientCloseNotifyMask);
120    // This seems to be semi-optional. For some reason, registering for any
121    // selection notify events seems to subscribe us to events for both the
122    // primary and the clipboard buffers. Register anyway just to be safe.
123    XFixesSelectSelectionInput(GetXDisplay(), GetX11RootWindow(),
124                               XA_PRIMARY,
125                               XFixesSetSelectionOwnerNotifyMask |
126                               XFixesSelectionWindowDestroyNotifyMask |
127                               XFixesSelectionClientCloseNotifyMask);
128
129    base::MessagePumpAuraX11::Current()->AddObserver(this);
130  }
131}
132
133SelectionChangeObserver::~SelectionChangeObserver() {
134  // We are a singleton; we will outlive our message pump.
135}
136
137SelectionChangeObserver* SelectionChangeObserver::GetInstance() {
138  return Singleton<SelectionChangeObserver>::get();
139}
140
141base::EventStatus SelectionChangeObserver::WillProcessEvent(
142    const base::NativeEvent& event) {
143  if (event->type == event_base_ + XFixesSelectionNotify) {
144    XFixesSelectionNotifyEvent* ev =
145        reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
146    if (ev->selection == clipboard_atom_) {
147      clipboard_sequence_number_++;
148    } else if (ev->selection == XA_PRIMARY) {
149      primary_sequence_number_++;
150    } else {
151      DLOG(ERROR) << "Unexpected selection atom: " << ev->selection;
152    }
153  }
154  return base::EVENT_CONTINUE;
155}
156
157///////////////////////////////////////////////////////////////////////////////
158
159// Represents the selection in different data formats. Binary data passed in is
160// assumed to be allocated with new char[], and is owned by FormatMap.
161class FormatMap {
162 public:
163  // Our internal data store, which we only expose through iterators.
164  typedef std::map< ::Atom, std::pair<char*, size_t> > InternalMap;
165  typedef std::map< ::Atom, std::pair<char*, size_t> >::const_iterator
166      const_iterator;
167
168  FormatMap();
169  ~FormatMap();
170
171  // Adds the selection in the format |atom|. Ownership of |data| is passed to
172  // us.
173  void Insert(::Atom atom, char* data, size_t size);
174
175  // Pass through to STL map. Only allow non-mutation access.
176  const_iterator begin() const { return data_.begin(); }
177  const_iterator end() const { return data_.end(); }
178  const_iterator find(::Atom atom) const { return data_.find(atom); }
179  size_t size() const { return data_.size(); }
180
181 private:
182  InternalMap data_;
183
184  DISALLOW_COPY_AND_ASSIGN(FormatMap);
185};
186
187FormatMap::FormatMap() {}
188
189FormatMap::~FormatMap() {
190  // WriteText() inserts the same pointer multiple times for different
191  // representations; we need to dedupe it.
192  std::set<char*> to_delete;
193  for (InternalMap::iterator it = data_.begin(); it != data_.end(); ++it)
194    to_delete.insert(it->second.first);
195
196  for (std::set<char*>::iterator it = to_delete.begin(); it != to_delete.end();
197       ++it) {
198    delete [] *it;
199  }
200}
201
202void FormatMap::Insert(::Atom atom, char* data, size_t size) {
203  DCHECK(data_.find(atom) == data_.end());
204  data_.insert(std::make_pair(atom, std::make_pair(data, size)));
205}
206
207///////////////////////////////////////////////////////////////////////////////
208
209// Represents a list of possible return types. Copy constructable.
210class TargetList {
211 public:
212  typedef std::vector< ::Atom> AtomVector;
213
214  TargetList(const AtomVector& target_list, X11AtomCache* atom_cache);
215
216  bool ContainsText() const;
217  bool ContainsFormat(const Clipboard::FormatType& format_type) const;
218  bool ContainsAtom(::Atom atom) const;
219
220 private:
221  AtomVector target_list_;
222  X11AtomCache* atom_cache_;
223};
224
225TargetList::TargetList(const AtomVector& target_list,
226                       X11AtomCache* atom_cache)
227    : target_list_(target_list),
228      atom_cache_(atom_cache) {
229}
230
231bool TargetList::ContainsText() const {
232  std::vector< ::Atom> atoms = GetTextAtomsFrom(atom_cache_);
233  for (std::vector< ::Atom>::const_iterator it = atoms.begin();
234       it != atoms.end(); ++it) {
235    if (ContainsAtom(*it))
236      return true;
237  }
238
239  return false;
240}
241
242bool TargetList::ContainsFormat(
243    const Clipboard::FormatType& format_type) const {
244  ::Atom atom = atom_cache_->GetAtom(format_type.ToString().c_str());
245  return ContainsAtom(atom);
246}
247
248bool TargetList::ContainsAtom(::Atom atom) const {
249  return find(target_list_.begin(), target_list_.end(), atom)
250      != target_list_.end();
251}
252
253///////////////////////////////////////////////////////////////////////////////
254
255// A holder for data with optional X11 deletion semantics.
256class SelectionData {
257 public:
258  // |atom_cache| is still owned by caller.
259  explicit SelectionData(X11AtomCache* atom_cache);
260  ~SelectionData();
261
262  ::Atom type() const { return type_; }
263  char* data() const { return data_; }
264  size_t size() const { return size_; }
265
266  void Set(::Atom type, char* data, size_t size, bool owned);
267
268  // If |type_| is a string type, convert the data to UTF8 and return it.
269  std::string GetText() const;
270
271  // Assigns the raw data to the string.
272  void AssignTo(std::string* result) const;
273
274 private:
275  ::Atom type_;
276  char* data_;
277  size_t size_;
278  bool owned_;
279
280  X11AtomCache* atom_cache_;
281};
282
283SelectionData::SelectionData(X11AtomCache* atom_cache)
284    : type_(None),
285      data_(NULL),
286      size_(0),
287      owned_(false),
288      atom_cache_(atom_cache) {
289}
290
291SelectionData::~SelectionData() {
292  if (owned_)
293    XFree(data_);
294}
295
296void SelectionData::Set(::Atom type, char* data, size_t size, bool owned) {
297  if (owned_)
298    XFree(data_);
299
300  type_ = type;
301  data_ = data;
302  size_ = size;
303  owned_ = owned;
304}
305
306std::string SelectionData::GetText() const {
307  if (type_ == atom_cache_->GetAtom(kUtf8String) ||
308      type_ == atom_cache_->GetAtom(kText)) {
309    return std::string(data_, size_);
310  } else if (type_ == atom_cache_->GetAtom(kString)) {
311    std::string result;
312    base::ConvertToUtf8AndNormalize(std::string(data_, size_),
313                                    base::kCodepageLatin1,
314                                    &result);
315    return result;
316  } else {
317    // BTW, I looked at COMPOUND_TEXT, and there's no way we're going to
318    // support that. Yuck.
319    NOTREACHED();
320    return std::string();
321  }
322}
323
324void SelectionData::AssignTo(std::string* result) const {
325  result->assign(data_, size_);
326}
327
328}  // namespace
329
330///////////////////////////////////////////////////////////////////////////////
331
332// I would love for the FormatType to really be a wrapper around an X11 ::Atom,
333// but there are a few problems. Chromeos unit tests spawn a new X11 server for
334// each test, so Atom numeric values don't persist across tests. We could still
335// maybe deal with that if we didn't have static accessor methods everywhere.
336
337Clipboard::FormatType::FormatType() {
338}
339
340Clipboard::FormatType::FormatType(const std::string& native_format)
341    : data_(native_format) {
342}
343
344Clipboard::FormatType::~FormatType() {
345}
346
347std::string Clipboard::FormatType::Serialize() const {
348  return data_;
349}
350
351// static
352Clipboard::FormatType Clipboard::FormatType::Deserialize(
353    const std::string& serialization) {
354  return FormatType(serialization);
355}
356
357bool Clipboard::FormatType::Equals(const FormatType& other) const {
358  return data_ == other.data_;
359}
360
361///////////////////////////////////////////////////////////////////////////////
362// Clipboard::AuraX11Details
363
364// Private implementation of our X11 integration. Keeps X11 headers out of the
365// majority of chrome, which break badly.
366class Clipboard::AuraX11Details : public base::MessagePumpDispatcher {
367 public:
368  AuraX11Details();
369  ~AuraX11Details();
370
371  X11AtomCache* atom_cache() { return &atom_cache_; }
372
373  // Returns the X11 type that we pass to various XSelection functions for the
374  // given buffer.
375  ::Atom LookupSelectionForBuffer(Buffer buffer) const;
376
377  // Finds the FormatMap for the incoming selection atom.
378  FormatMap* LookupStorageForAtom(::Atom atom);
379
380  // As we need to collect all the data types before we tell X11 that we own a
381  // particular selection, we create a temporary clipboard mapping that
382  // InsertMapping writes to. Then we commit it in TakeOwnershipOfSelection,
383  // where we save it in one of the clipboard data slots.
384  void CreateNewClipboardData();
385
386  // Inserts a mapping into clipboard_data_.
387  void InsertMapping(const std::string& key, char* data, size_t data_len);
388
389  // Moves the temporary |clipboard_data_| to the long term data storage for
390  // |buffer|.
391  void TakeOwnershipOfSelection(Buffer buffer);
392
393  // Returns the first of |types| offered by the current selection holder in
394  // |data_out|, or returns NULL if none of those types are available.
395  //
396  // If the selection holder is us, this call is synchronous and we pull
397  // the data out of |clipboard_selection_| or |primary_selection_|. If the
398  // selection holder is some other window, we spin up a nested message loop
399  // and do the asynchronous dance with whatever application is holding the
400  // selection.
401  scoped_ptr<SelectionData> RequestAndWaitForTypes(
402      Buffer buffer,
403      const std::vector< ::Atom>& types);
404
405  // Retrieves the list of possible data types the current clipboard owner has.
406  //
407  // If the selection holder is us, this is synchronous, otherwise this runs a
408  // blocking message loop.
409  TargetList WaitAndGetTargetsList(Buffer buffer);
410
411  // Does the work of requesting |target| from |selection_name|, spinning up
412  // the nested message loop, and reading the resulting data back. |out_data|
413  // is allocated with the X allocator and must be freed with
414  // XFree(). |out_data_bytes| is the length in machine chars, while
415  // |out_data_items| is the length in |out_type| items.
416  bool PerformBlockingConvertSelection(::Atom selection_name,
417                                       ::Atom target,
418                                       unsigned char** out_data,
419                                       size_t* out_data_bytes,
420                                       size_t* out_data_items,
421                                       ::Atom* out_type);
422
423  // Returns a list of all text atoms that we handle.
424  std::vector< ::Atom> GetTextAtoms() const;
425
426  // Returns a vector with a |format| converted to an X11 atom.
427  std::vector< ::Atom> GetAtomsForFormat(const Clipboard::FormatType& format);
428
429  // Clears a certain data buffer.
430  void Clear(Buffer buffer);
431
432 private:
433  // Called by Dispatch to handle specific types of events.
434  void HandleSelectionRequest(const XSelectionRequestEvent& event);
435  void HandleSelectionNotify(const XSelectionEvent& event);
436  void HandleSelectionClear(const XSelectionClearEvent& event);
437  void HandlePropertyNotify(const XPropertyEvent& event);
438
439  // Overridden from base::MessagePumpDispatcher:
440  virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE;
441
442  // Temporary target map that we write to during DispatchObects.
443  scoped_ptr<FormatMap> clipboard_data_;
444
445  // The current value of our clipboard and primary selections. These should be
446  // non-NULL when we own the selection.
447  scoped_ptr<FormatMap> clipboard_selection_;
448  scoped_ptr<FormatMap> primary_selection_;
449
450  // Our X11 state.
451  Display* x_display_;
452  ::Window x_root_window_;
453
454  // Input-only window used as a selection owner.
455  ::Window x_window_;
456
457  // True if we're currently running a nested message loop, waiting for data to
458  // come back from the X server.
459  bool in_nested_loop_;
460
461  // Data to the current XConvertSelection request. Used for error detection;
462  // we verify it on the return message.
463  ::Atom current_selection_;
464  ::Atom current_target_;
465
466  // The property in the returning SelectNotify message is used to signal
467  // success. If None, our request failed somehow. If equal to the property
468  // atom that we sent in the XConvertSelection call, we can read that property
469  // on |x_window_| for the requested data.
470  ::Atom returned_property_;
471
472  // Called to terminate the nested message loop.
473  base::Closure quit_closure_;
474
475  X11AtomCache atom_cache_;
476
477  DISALLOW_COPY_AND_ASSIGN(AuraX11Details);
478};
479
480Clipboard::AuraX11Details::AuraX11Details()
481    : x_display_(GetXDisplay()),
482      x_root_window_(DefaultRootWindow(x_display_)),
483      in_nested_loop_(false),
484      atom_cache_(x_display_, kAtomsToCache) {
485  // We don't know all possible MIME types at compile time.
486  atom_cache_.allow_uncached_atoms();
487
488  x_window_ = XCreateWindow(
489      x_display_, x_root_window_,
490      -100, -100, 10, 10,  // x, y, width, height
491      0,                   // border width
492      CopyFromParent,      // depth
493      InputOnly,
494      CopyFromParent,      // visual
495      0,
496      NULL);
497  XStoreName(x_display_, x_window_, "Chromium clipboard");
498  XSelectInput(x_display_, x_window_, PropertyChangeMask);
499
500  base::MessagePumpAuraX11::Current()->AddDispatcherForWindow(this, x_window_);
501}
502
503Clipboard::AuraX11Details::~AuraX11Details() {
504  base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(x_window_);
505
506  XDestroyWindow(x_display_, x_window_);
507}
508
509::Atom Clipboard::AuraX11Details::LookupSelectionForBuffer(
510    Buffer buffer) const {
511  if (buffer == BUFFER_STANDARD)
512    return atom_cache_.GetAtom(kClipboard);
513  else
514    return XA_PRIMARY;
515}
516
517FormatMap* Clipboard::AuraX11Details::LookupStorageForAtom(::Atom atom) {
518  if (atom == XA_PRIMARY)
519    return primary_selection_.get();
520  else if (atom == atom_cache_.GetAtom(kClipboard))
521    return clipboard_selection_.get();
522  else
523    return NULL;
524}
525
526void Clipboard::AuraX11Details::CreateNewClipboardData() {
527  clipboard_data_.reset(new FormatMap);
528}
529
530void Clipboard::AuraX11Details::InsertMapping(const std::string& key,
531                                              char* data,
532                                              size_t data_len) {
533  ::Atom atom_key = atom_cache_.GetAtom(key.c_str());
534  clipboard_data_->Insert(atom_key, data, data_len);
535}
536
537void Clipboard::AuraX11Details::TakeOwnershipOfSelection(Buffer buffer) {
538  // Tell the X server that we are now the selection owner.
539  ::Atom xselection = LookupSelectionForBuffer(buffer);
540  XSetSelectionOwner(x_display_, xselection, x_window_, CurrentTime);
541
542  if (XGetSelectionOwner(x_display_, xselection) == x_window_) {
543    // The X server agrees that we are the selection owner. Commit our data.
544    if (buffer == BUFFER_STANDARD)
545      clipboard_selection_ = clipboard_data_.Pass();
546    else
547      primary_selection_ = clipboard_data_.Pass();
548  }
549}
550
551scoped_ptr<SelectionData> Clipboard::AuraX11Details::RequestAndWaitForTypes(
552    Buffer buffer,
553    const std::vector< ::Atom>& types) {
554  ::Atom selection_name = LookupSelectionForBuffer(buffer);
555  if (XGetSelectionOwner(x_display_, selection_name) == x_window_) {
556    // We can local fastpath instead of playing the nested message loop game
557    // with the X server.
558    FormatMap* format_map = LookupStorageForAtom(selection_name);
559    DCHECK(format_map);
560
561    for (std::vector< ::Atom>::const_iterator it = types.begin();
562         it != types.end(); ++it) {
563      FormatMap::const_iterator format_map_it = format_map->find(*it);
564      if (format_map_it != format_map->end()) {
565        scoped_ptr<SelectionData> data_out(new SelectionData(&atom_cache_));
566        data_out->Set(format_map_it->first, format_map_it->second.first,
567                      format_map_it->second.second, false);
568        return data_out.Pass();
569      }
570    }
571  } else {
572    TargetList targets = WaitAndGetTargetsList(buffer);
573
574    for (std::vector< ::Atom>::const_iterator it = types.begin();
575         it != types.end(); ++it) {
576      unsigned char* data = NULL;
577      size_t data_bytes = 0;
578      ::Atom type = None;
579      if (targets.ContainsAtom(*it) &&
580          PerformBlockingConvertSelection(selection_name,
581                                          *it,
582                                          &data,
583                                          &data_bytes,
584                                          NULL,
585                                          &type) &&
586          type == *it) {
587        scoped_ptr<SelectionData> data_out(new SelectionData(&atom_cache_));
588        data_out->Set(type, (char*)data, data_bytes, true);
589        return data_out.Pass();
590      }
591    }
592  }
593
594  return scoped_ptr<SelectionData>();
595}
596
597TargetList Clipboard::AuraX11Details::WaitAndGetTargetsList(
598    Buffer buffer) {
599  ::Atom selection_name = LookupSelectionForBuffer(buffer);
600  std::vector< ::Atom> out;
601  if (XGetSelectionOwner(x_display_, selection_name) == x_window_) {
602    // We can local fastpath and return the list of local targets.
603    FormatMap* format_map = LookupStorageForAtom(selection_name);
604    DCHECK(format_map);
605
606    for (FormatMap::const_iterator it = format_map->begin();
607         it != format_map->end(); ++it) {
608      out.push_back(it->first);
609    }
610  } else {
611    unsigned char* data = NULL;
612    size_t out_data_items = 0;
613    ::Atom out_type = None;
614
615    if (PerformBlockingConvertSelection(selection_name,
616                                        atom_cache_.GetAtom(kTargets),
617                                        &data,
618                                        NULL,
619                                        &out_data_items,
620                                        &out_type)) {
621      ::Atom* atom_array = reinterpret_cast< ::Atom*>(data);
622      for (size_t i = 0; i < out_data_items; ++i)
623        out.push_back(atom_array[i]);
624
625      XFree(data);
626    } else {
627      // There was no target list. Most Java apps doesn't offer a TARGETS list,
628      // even though they AWT to. They will offer individual text types if you
629      // ask. If this is the case we attempt to make sense of the contents as
630      // text. This is pretty unfortunate since it means we have to actually
631      // copy the data to see if it is available, but at least this path
632      // shouldn't be hit for conforming programs.
633      std::vector< ::Atom> types = GetTextAtoms();
634      for (std::vector< ::Atom>::const_iterator it = types.begin();
635           it != types.end(); ++it) {
636        ::Atom type = None;
637        if (PerformBlockingConvertSelection(selection_name,
638                                            *it,
639                                            NULL,
640                                            NULL,
641                                            NULL,
642                                            &type) &&
643            type == *it) {
644          out.push_back(*it);
645        }
646      }
647    }
648  }
649
650  return TargetList(out, &atom_cache_);
651}
652
653bool Clipboard::AuraX11Details::PerformBlockingConvertSelection(
654    ::Atom selection_name,
655    ::Atom target,
656    unsigned char** out_data,
657    size_t* out_data_bytes,
658    size_t* out_data_items,
659    ::Atom* out_type) {
660  // The name of the property we're asking to be set on |x_window_|.
661  ::Atom property_to_set = atom_cache_.GetAtom(kChromeSelection);
662
663  XConvertSelection(x_display_,
664                    selection_name,
665                    target,
666                    property_to_set,
667                    x_window_,
668                    CurrentTime);
669
670  // Now that we've thrown our message off to the X11 server, we block waiting
671  // for a response.
672  MessageLoopForUI* loop = MessageLoopForUI::current();
673  MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
674  base::RunLoop run_loop(base::MessagePumpAuraX11::Current());
675
676  current_selection_ = selection_name;
677  current_target_ = target;
678  in_nested_loop_ = true;
679  quit_closure_ = run_loop.QuitClosure();
680  run_loop.Run();
681  in_nested_loop_ = false;
682  current_selection_ = None;
683  current_target_ = None;
684
685  if (returned_property_ != property_to_set)
686    return false;
687
688  // Retrieve the data from our window.
689  unsigned long nitems = 0;
690  unsigned long nbytes = 0;
691  Atom prop_type = None;
692  int prop_format = 0;
693  unsigned char* property_data = NULL;
694  if (XGetWindowProperty(x_display_,
695                         x_window_,
696                         returned_property_,
697                         0, 0x1FFFFFFF /* MAXINT32 / 4 */, False,
698                         AnyPropertyType, &prop_type, &prop_format,
699                         &nitems, &nbytes, &property_data) != Success) {
700    return false;
701  }
702
703  if (prop_type == None)
704    return false;
705
706  if (out_data)
707    *out_data = property_data;
708
709  if (out_data_bytes) {
710    // So even though we should theoretically have nbytes (and we can't
711    // pass NULL there), we need to manually calculate the byte length here
712    // because nbytes always returns zero.
713    switch (prop_format) {
714      case 8:
715        *out_data_bytes = nitems;
716        break;
717      case 16:
718        *out_data_bytes = sizeof(short) * nitems;
719        break;
720      case 32:
721        *out_data_bytes = sizeof(long) * nitems;
722        break;
723      default:
724        NOTREACHED();
725        break;
726    }
727  }
728
729  if (out_data_items)
730    *out_data_items = nitems;
731
732  if (out_type)
733    *out_type = prop_type;
734
735  return true;
736}
737
738std::vector< ::Atom> Clipboard::AuraX11Details::GetTextAtoms() const {
739  return GetTextAtomsFrom(&atom_cache_);
740}
741
742std::vector< ::Atom> Clipboard::AuraX11Details::GetAtomsForFormat(
743    const Clipboard::FormatType& format) {
744  std::vector< ::Atom> atoms;
745  atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str()));
746  return atoms;
747}
748
749void Clipboard::AuraX11Details::Clear(Buffer buffer) {
750  ::Atom selection_name = LookupSelectionForBuffer(buffer);
751  if (XGetSelectionOwner(x_display_, selection_name) == x_window_)
752    XSetSelectionOwner(x_display_, selection_name, None, CurrentTime);
753
754  if (buffer == BUFFER_STANDARD)
755    clipboard_selection_.reset();
756  else
757    primary_selection_.reset();
758}
759
760void Clipboard::AuraX11Details::HandleSelectionRequest(
761    const XSelectionRequestEvent& event) {
762  // Incrementally build our selection. By default this is a refusal, and we'll
763  // override the parts indicating success in the different cases.
764  XEvent reply;
765  reply.xselection.type = SelectionNotify;
766  reply.xselection.requestor = event.requestor;
767  reply.xselection.selection = event.selection;
768  reply.xselection.target = event.target;
769  reply.xselection.property = None;  // Indicates failure
770  reply.xselection.time = event.time;
771
772  // Get the proper selection.
773  FormatMap* format_map = LookupStorageForAtom(event.selection);
774  if (format_map) {
775    ::Atom targets_atom = atom_cache_.GetAtom(kTargets);
776    if (event.target == targets_atom) {
777      std::vector< ::Atom> targets;
778      targets.push_back(targets_atom);
779      for (FormatMap::const_iterator it = format_map->begin();
780           it != format_map->end(); ++it) {
781        targets.push_back(it->first);
782      }
783
784      XChangeProperty(x_display_, event.requestor, event.property, XA_ATOM, 32,
785                      PropModeReplace,
786                      reinterpret_cast<unsigned char*>(&targets.front()),
787                      targets.size());
788      reply.xselection.property = event.property;
789    } else if (event.target == atom_cache_.GetAtom(kMultiple)) {
790      // TODO(erg): Theoretically, the spec claims I'm supposed to handle the
791      // MULTIPLE case, but I haven't seen it in the wild yet.
792      NOTIMPLEMENTED();
793    } else {
794      // Try to find the data type in map.
795      FormatMap::const_iterator it = format_map->find(event.target);
796      if (it != format_map->end()) {
797        XChangeProperty(x_display_, event.requestor, event.property,
798                        event.target, 8,
799                        PropModeReplace,
800                        reinterpret_cast<unsigned char*>(it->second.first),
801                        it->second.second);
802        reply.xselection.property = event.property;
803      }
804      // I would put error logging here, but GTK ignores TARGETS and spams us
805      // looking for its own internal types.
806    }
807  } else {
808    DLOG(ERROR) << "Requested on a selection we don't support: "
809                << XGetAtomName(x_display_, event.selection);
810  }
811
812  // Send off the reply.
813  XSendEvent(x_display_, event.requestor, False, 0, &reply);
814}
815
816void Clipboard::AuraX11Details::HandleSelectionNotify(
817    const XSelectionEvent& event) {
818  if (!in_nested_loop_) {
819    // This shouldn't happen; we're not waiting on the X server for data, but
820    // any client can send any message...
821    return;
822  }
823
824  if (current_selection_ == event.selection &&
825      current_target_ == event.target) {
826    returned_property_ = event.property;
827  } else {
828    // I am assuming that if some other client sent us a message after we've
829    // asked for data, but it's malformed, we should just treat as if they sent
830    // us an error message.
831    returned_property_ = None;
832  }
833
834  quit_closure_.Run();
835}
836
837void Clipboard::AuraX11Details::HandleSelectionClear(
838    const XSelectionClearEvent& event) {
839  DLOG(ERROR) << "SelectionClear";
840
841  // TODO(erg): If we receive a SelectionClear event while we're handling data,
842  // we need to delay clearing.
843}
844
845void Clipboard::AuraX11Details::HandlePropertyNotify(
846    const XPropertyEvent& event) {
847  // TODO(erg): Must handle PropertyNotify events on our |x_window_| as part
848  // of receiving data during paste.
849}
850
851bool Clipboard::AuraX11Details::Dispatch(const base::NativeEvent& event) {
852  XEvent* xev = event;
853
854  switch (xev->type) {
855    case SelectionRequest:
856      HandleSelectionRequest(xev->xselectionrequest);
857      break;
858    case SelectionNotify:
859      HandleSelectionNotify(xev->xselection);
860      break;
861    case SelectionClear:
862      HandleSelectionClear(xev->xselectionclear);
863      break;
864    case PropertyNotify:
865      HandlePropertyNotify(xev->xproperty);
866      break;
867    default:
868      break;
869  }
870
871  return true;
872}
873
874///////////////////////////////////////////////////////////////////////////////
875// Clipboard
876
877Clipboard::Clipboard()
878    : aurax11_details_(new AuraX11Details) {
879  DCHECK(CalledOnValidThread());
880}
881
882Clipboard::~Clipboard() {
883  DCHECK(CalledOnValidThread());
884
885  // TODO(erg): We need to do whatever the equivalent of
886  // gtk_clipboard_store(clipboard_) is here. When we shut down, we want the
887  // current selection to live on.
888}
889
890void Clipboard::WriteObjectsImpl(Buffer buffer,
891                                 const ObjectMap& objects,
892                                 SourceTag tag) {
893  DCHECK(CalledOnValidThread());
894  DCHECK(IsValidBuffer(buffer));
895
896  aurax11_details_->CreateNewClipboardData();
897  for (ObjectMap::const_iterator iter = objects.begin();
898       iter != objects.end(); ++iter) {
899    DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
900  }
901  WriteSourceTag(tag);
902  aurax11_details_->TakeOwnershipOfSelection(buffer);
903
904  if (buffer == BUFFER_STANDARD) {
905    ObjectMap::const_iterator text_iter = objects.find(CBF_TEXT);
906    if (text_iter != objects.end()) {
907      aurax11_details_->CreateNewClipboardData();
908      const ObjectMapParam& char_vector = text_iter->second[0];
909      WriteText(&char_vector.front(), char_vector.size());
910      WriteSourceTag(tag);
911      aurax11_details_->TakeOwnershipOfSelection(BUFFER_SELECTION);
912    }
913  }
914}
915
916bool Clipboard::IsFormatAvailable(const FormatType& format,
917                                  Buffer buffer) const {
918  DCHECK(CalledOnValidThread());
919  DCHECK(IsValidBuffer(buffer));
920
921  TargetList target_list = aurax11_details_->WaitAndGetTargetsList(buffer);
922  return target_list.ContainsFormat(format);
923}
924
925void Clipboard::Clear(Buffer buffer) {
926  DCHECK(CalledOnValidThread());
927  DCHECK(IsValidBuffer(buffer));
928  aurax11_details_->Clear(buffer);
929}
930
931void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types,
932    bool* contains_filenames) const {
933  DCHECK(CalledOnValidThread());
934  if (!types || !contains_filenames) {
935    NOTREACHED();
936    return;
937  }
938
939  TargetList target_list = aurax11_details_->WaitAndGetTargetsList(buffer);
940
941  types->clear();
942
943  if (target_list.ContainsText())
944    types->push_back(UTF8ToUTF16(kMimeTypeText));
945  if (target_list.ContainsFormat(GetHtmlFormatType()))
946    types->push_back(UTF8ToUTF16(kMimeTypeHTML));
947  if (target_list.ContainsFormat(GetRtfFormatType()))
948    types->push_back(UTF8ToUTF16(kMimeTypeRTF));
949  if (target_list.ContainsFormat(GetBitmapFormatType()))
950    types->push_back(UTF8ToUTF16(kMimeTypePNG));
951  *contains_filenames = false;
952
953  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
954      buffer,
955      aurax11_details_->GetAtomsForFormat(GetWebCustomDataFormatType())));
956  if (!data.get())
957    return;
958
959  ReadCustomDataTypes(data->data(), data->size(), types);
960}
961
962void Clipboard::ReadText(Buffer buffer, string16* result) const {
963  DCHECK(CalledOnValidThread());
964  ReportAction(buffer, READ_TEXT);
965
966  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
967      buffer, aurax11_details_->GetTextAtoms()));
968  if (data.get()) {
969    std::string text = data->GetText();
970    *result = UTF8ToUTF16(text);
971  }
972}
973
974void Clipboard::ReadAsciiText(Buffer buffer, std::string* result) const {
975  DCHECK(CalledOnValidThread());
976  ReportAction(buffer, READ_TEXT);
977
978  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
979      buffer, aurax11_details_->GetTextAtoms()));
980  if (data.get())
981    result->assign(data->GetText());
982}
983
984// TODO(estade): handle different charsets.
985// TODO(port): set *src_url.
986void Clipboard::ReadHTML(Buffer buffer,
987                         string16* markup,
988                         std::string* src_url,
989                         uint32* fragment_start,
990                         uint32* fragment_end) const {
991  DCHECK(CalledOnValidThread());
992  markup->clear();
993  if (src_url)
994    src_url->clear();
995  *fragment_start = 0;
996  *fragment_end = 0;
997
998  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
999      buffer, aurax11_details_->GetAtomsForFormat(GetHtmlFormatType())));
1000  if (!data.get())
1001    return;
1002
1003  // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is
1004  // UTF-16, otherwise assume UTF-8.
1005  if (data->size() >= 2 &&
1006      reinterpret_cast<const uint16_t*>(data->data())[0] == 0xFEFF) {
1007    markup->assign(reinterpret_cast<const uint16_t*>(data->data()) + 1,
1008                   (data->size() / 2) - 1);
1009  } else {
1010    UTF8ToUTF16(reinterpret_cast<const char*>(data->data()), data->size(),
1011                markup);
1012  }
1013
1014  // If there is a terminating NULL, drop it.
1015  if (!markup->empty() && markup->at(markup->length() - 1) == '\0')
1016    markup->resize(markup->length() - 1);
1017
1018  *fragment_start = 0;
1019  DCHECK(markup->length() <= kuint32max);
1020  *fragment_end = static_cast<uint32>(markup->length());
1021}
1022
1023void Clipboard::ReadRTF(Buffer buffer, std::string* result) const {
1024  DCHECK(CalledOnValidThread());
1025  ReportAction(buffer, READ_TEXT);
1026
1027  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
1028      buffer, aurax11_details_->GetAtomsForFormat(GetRtfFormatType())));
1029  if (data.get())
1030    data->AssignTo(result);
1031}
1032
1033SkBitmap Clipboard::ReadImage(Buffer buffer) const {
1034  DCHECK(CalledOnValidThread());
1035  NOTIMPLEMENTED();
1036  return SkBitmap();
1037}
1038
1039void Clipboard::ReadCustomData(Buffer buffer,
1040                               const string16& type,
1041                               string16* result) const {
1042  DCHECK(CalledOnValidThread());
1043
1044  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
1045      buffer,
1046      aurax11_details_->GetAtomsForFormat(GetWebCustomDataFormatType())));
1047  if (!data.get())
1048    return;
1049
1050  ReadCustomDataForType(data->data(), data->size(), type, result);
1051}
1052
1053void Clipboard::ReadBookmark(string16* title, std::string* url) const {
1054  DCHECK(CalledOnValidThread());
1055  // TODO(erg): This was left NOTIMPLEMENTED() in the gtk port too.
1056  NOTIMPLEMENTED();
1057}
1058
1059void Clipboard::ReadData(const FormatType& format, std::string* result) const {
1060  ReadDataImpl(BUFFER_STANDARD, format, result);
1061}
1062
1063void Clipboard::ReadDataImpl(Buffer buffer,
1064                             const FormatType& format,
1065                             std::string* result) const {
1066  DCHECK(CalledOnValidThread());
1067
1068  scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes(
1069      buffer, aurax11_details_->GetAtomsForFormat(format)));
1070  if (data.get())
1071    data->AssignTo(result);
1072}
1073
1074Clipboard::SourceTag Clipboard::ReadSourceTag(Buffer buffer) const {
1075  std::string result;
1076  ReadDataImpl(buffer, GetSourceTagFormatType(), &result);
1077  return Binary2SourceTag(result);
1078}
1079
1080uint64 Clipboard::GetSequenceNumber(Buffer buffer) {
1081  DCHECK(CalledOnValidThread());
1082  if (buffer == BUFFER_STANDARD)
1083    return SelectionChangeObserver::GetInstance()->clipboard_sequence_number();
1084  else
1085    return SelectionChangeObserver::GetInstance()->primary_sequence_number();
1086}
1087
1088void Clipboard::WriteText(const char* text_data, size_t text_len) {
1089  char* data = new char[text_len];
1090  memcpy(data, text_data, text_len);
1091
1092  aurax11_details_->InsertMapping(kMimeTypeText, data, text_len);
1093  aurax11_details_->InsertMapping(kText, data, text_len);
1094  aurax11_details_->InsertMapping(kString, data, text_len);
1095  aurax11_details_->InsertMapping(kUtf8String, data, text_len);
1096}
1097
1098void Clipboard::WriteHTML(const char* markup_data,
1099                          size_t markup_len,
1100                          const char* url_data,
1101                          size_t url_len) {
1102  // TODO(estade): We need to expand relative links with |url_data|.
1103  static const char* html_prefix = "<meta http-equiv=\"content-type\" "
1104                                   "content=\"text/html; charset=utf-8\">";
1105  size_t html_prefix_len = strlen(html_prefix);
1106  size_t total_len = html_prefix_len + markup_len + 1;
1107
1108  char* data = new char[total_len];
1109  snprintf(data, total_len, "%s", html_prefix);
1110  memcpy(data + html_prefix_len, markup_data, markup_len);
1111  // Some programs expect NULL-terminated data. See http://crbug.com/42624
1112  data[total_len - 1] = '\0';
1113
1114  aurax11_details_->InsertMapping(kMimeTypeHTML, data, total_len);
1115}
1116
1117void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) {
1118  WriteData(GetRtfFormatType(), rtf_data, data_len);
1119}
1120
1121void Clipboard::WriteBookmark(const char* title_data,
1122                              size_t title_len,
1123                              const char* url_data,
1124                              size_t url_len) {
1125  // Write as a mozilla url (UTF16: URL, newline, title).
1126  string16 url = UTF8ToUTF16(std::string(url_data, url_len) + "\n");
1127  string16 title = UTF8ToUTF16(std::string(title_data, title_len));
1128  int data_len = 2 * (title.length() + url.length());
1129
1130  char* data = new char[data_len];
1131  memcpy(data, url.data(), 2 * url.length());
1132  memcpy(data + 2 * url.length(), title.data(), 2 * title.length());
1133  aurax11_details_->InsertMapping(kMimeTypeMozillaURL, data, data_len);
1134}
1135
1136// Write an extra flavor that signifies WebKit was the last to modify the
1137// pasteboard. This flavor has no data.
1138void Clipboard::WriteWebSmartPaste() {
1139  aurax11_details_->InsertMapping(kMimeTypeWebkitSmartPaste, NULL, 0);
1140}
1141
1142void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) {
1143  // TODO(erg): I'm not sure if we should be writting BMP data here or
1144  // not. It's what the GTK port does, but I'm not sure it's the right thing to
1145  // do.
1146  NOTIMPLEMENTED();
1147}
1148
1149void Clipboard::WriteData(const FormatType& format,
1150                          const char* data_data,
1151                          size_t data_len) {
1152  // We assume that certain mapping types are only written by trusted code.
1153  // Therefore we must upkeep their integrity.
1154  if (format.Equals(GetBitmapFormatType()))
1155    return;
1156  char* data = new char[data_len];
1157  memcpy(data, data_data, data_len);
1158  aurax11_details_->InsertMapping(format.ToString(), data, data_len);
1159}
1160
1161void Clipboard::WriteSourceTag(SourceTag tag) {
1162  if (tag != SourceTag()) {
1163    ObjectMapParam binary = SourceTag2Binary(tag);
1164    WriteData(GetSourceTagFormatType(), &binary[0], binary.size());
1165  }
1166}
1167
1168// static
1169Clipboard::FormatType Clipboard::GetFormatType(
1170    const std::string& format_string) {
1171  return FormatType::Deserialize(format_string);
1172}
1173
1174// static
1175const Clipboard::FormatType& Clipboard::GetUrlFormatType() {
1176  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeURIList));
1177  return type;
1178}
1179
1180// static
1181const Clipboard::FormatType& Clipboard::GetUrlWFormatType() {
1182  return GetUrlFormatType();
1183}
1184
1185// static
1186const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() {
1187  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeText));
1188  return type;
1189}
1190
1191// static
1192const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() {
1193  return GetPlainTextFormatType();
1194}
1195
1196// static
1197const Clipboard::FormatType& Clipboard::GetFilenameFormatType() {
1198  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeFilename));
1199  return type;
1200}
1201
1202// static
1203const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() {
1204  return Clipboard::GetFilenameFormatType();
1205}
1206
1207// static
1208const Clipboard::FormatType& Clipboard::GetHtmlFormatType() {
1209  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeHTML));
1210  return type;
1211}
1212
1213// static
1214const Clipboard::FormatType& Clipboard::GetRtfFormatType() {
1215  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeRTF));
1216  return type;
1217}
1218
1219// static
1220const Clipboard::FormatType& Clipboard::GetBitmapFormatType() {
1221  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeBitmap));
1222  return type;
1223}
1224
1225// static
1226const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() {
1227  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebkitSmartPaste));
1228  return type;
1229}
1230
1231// static
1232const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() {
1233  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData));
1234  return type;
1235}
1236
1237// static
1238const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() {
1239  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypePepperCustomData));
1240  return type;
1241}
1242
1243// static
1244const Clipboard::FormatType& Clipboard::GetSourceTagFormatType() {
1245  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kSourceTagType));
1246  return type;
1247}
1248
1249}  // namespace ui
1250