| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/clipboard/clipboard.h" |
| |
| #include <X11/extensions/Xfixes.h> |
| #include <X11/Xatom.h> |
| #include <list> |
| #include <set> |
| |
| #include "base/basictypes.h" |
| #include "base/file_path.h" |
| #include "base/i18n/icu_string_conversions.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/singleton.h" |
| #include "base/message_pump_aurax11.h" |
| #include "base/message_pump_observer.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/clipboard/custom_data_helper.h" |
| #include "ui/base/x/x11_atom_cache.h" |
| #include "ui/base/x/x11_util.h" |
| |
| #include "ui/gfx/size.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| const char kChromeSelection[] = "CHROME_SELECTION"; |
| const char kClipboard[] = "CLIPBOARD"; |
| const char kMimeTypeBitmap[] = "image/bmp"; |
| const char kMimeTypeFilename[] = "chromium/filename"; |
| const char kMimeTypeMozillaURL[] = "text/x-moz-url"; |
| const char kMimeTypePepperCustomData[] = "chromium/x-pepper-custom-data"; |
| const char kMimeTypeWebkitSmartPaste[] = "chromium/x-webkit-paste"; |
| const char kMultiple[] = "MULTIPLE"; |
| const char kSourceTagType[] = "org.chromium.source-tag"; |
| const char kString[] = "STRING"; |
| const char kTargets[] = "TARGETS"; |
| const char kText[] = "TEXT"; |
| const char kUtf8String[] = "UTF8_STRING"; |
| |
| const char* kAtomsToCache[] = { |
| kChromeSelection, |
| kClipboard, |
| kMimeTypeBitmap, |
| kMimeTypeFilename, |
| kMimeTypeMozillaURL, |
| kMimeTypeWebkitSmartPaste, |
| kMultiple, |
| kSourceTagType, |
| kString, |
| kTargets, |
| kText, |
| kUtf8String, |
| NULL |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // Returns a list of all text atoms that we handle. |
| std::vector< ::Atom> GetTextAtomsFrom(const X11AtomCache* atom_cache) { |
| std::vector< ::Atom> atoms; |
| atoms.push_back(atom_cache->GetAtom(kUtf8String)); |
| atoms.push_back(atom_cache->GetAtom(kString)); |
| atoms.push_back(atom_cache->GetAtom(kText)); |
| return atoms; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // Uses the XFixes API to provide sequence numbers for GetSequenceNumber(). |
| class SelectionChangeObserver : public base::MessagePumpObserver { |
| public: |
| static SelectionChangeObserver* GetInstance(); |
| |
| uint64 clipboard_sequence_number() const { |
| return clipboard_sequence_number_; |
| } |
| uint64 primary_sequence_number() const { return primary_sequence_number_; } |
| |
| private: |
| friend struct DefaultSingletonTraits<SelectionChangeObserver>; |
| |
| SelectionChangeObserver(); |
| ~SelectionChangeObserver(); |
| |
| // Overridden from base::MessagePumpObserver: |
| virtual base::EventStatus WillProcessEvent( |
| const base::NativeEvent& event) OVERRIDE; |
| virtual void DidProcessEvent( |
| const base::NativeEvent& event) OVERRIDE {} |
| |
| int event_base_; |
| Atom clipboard_atom_; |
| uint64 clipboard_sequence_number_; |
| uint64 primary_sequence_number_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SelectionChangeObserver); |
| }; |
| |
| SelectionChangeObserver::SelectionChangeObserver() |
| : event_base_(-1), |
| clipboard_atom_(None), |
| clipboard_sequence_number_(0), |
| primary_sequence_number_(0) { |
| int ignored; |
| if (XFixesQueryExtension(GetXDisplay(), &event_base_, &ignored)) { |
| clipboard_atom_ = XInternAtom(GetXDisplay(), kClipboard, false); |
| XFixesSelectSelectionInput(GetXDisplay(), GetX11RootWindow(), |
| clipboard_atom_, |
| XFixesSetSelectionOwnerNotifyMask | |
| XFixesSelectionWindowDestroyNotifyMask | |
| XFixesSelectionClientCloseNotifyMask); |
| // This seems to be semi-optional. For some reason, registering for any |
| // selection notify events seems to subscribe us to events for both the |
| // primary and the clipboard buffers. Register anyway just to be safe. |
| XFixesSelectSelectionInput(GetXDisplay(), GetX11RootWindow(), |
| XA_PRIMARY, |
| XFixesSetSelectionOwnerNotifyMask | |
| XFixesSelectionWindowDestroyNotifyMask | |
| XFixesSelectionClientCloseNotifyMask); |
| |
| base::MessagePumpAuraX11::Current()->AddObserver(this); |
| } |
| } |
| |
| SelectionChangeObserver::~SelectionChangeObserver() { |
| // We are a singleton; we will outlive our message pump. |
| } |
| |
| SelectionChangeObserver* SelectionChangeObserver::GetInstance() { |
| return Singleton<SelectionChangeObserver>::get(); |
| } |
| |
| base::EventStatus SelectionChangeObserver::WillProcessEvent( |
| const base::NativeEvent& event) { |
| if (event->type == event_base_ + XFixesSelectionNotify) { |
| XFixesSelectionNotifyEvent* ev = |
| reinterpret_cast<XFixesSelectionNotifyEvent*>(event); |
| if (ev->selection == clipboard_atom_) { |
| clipboard_sequence_number_++; |
| } else if (ev->selection == XA_PRIMARY) { |
| primary_sequence_number_++; |
| } else { |
| DLOG(ERROR) << "Unexpected selection atom: " << ev->selection; |
| } |
| } |
| return base::EVENT_CONTINUE; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // Represents the selection in different data formats. Binary data passed in is |
| // assumed to be allocated with new char[], and is owned by FormatMap. |
| class FormatMap { |
| public: |
| // Our internal data store, which we only expose through iterators. |
| typedef std::map< ::Atom, std::pair<char*, size_t> > InternalMap; |
| typedef std::map< ::Atom, std::pair<char*, size_t> >::const_iterator |
| const_iterator; |
| |
| FormatMap(); |
| ~FormatMap(); |
| |
| // Adds the selection in the format |atom|. Ownership of |data| is passed to |
| // us. |
| void Insert(::Atom atom, char* data, size_t size); |
| |
| // Pass through to STL map. Only allow non-mutation access. |
| const_iterator begin() const { return data_.begin(); } |
| const_iterator end() const { return data_.end(); } |
| const_iterator find(::Atom atom) const { return data_.find(atom); } |
| size_t size() const { return data_.size(); } |
| |
| private: |
| InternalMap data_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FormatMap); |
| }; |
| |
| FormatMap::FormatMap() {} |
| |
| FormatMap::~FormatMap() { |
| // WriteText() inserts the same pointer multiple times for different |
| // representations; we need to dedupe it. |
| std::set<char*> to_delete; |
| for (InternalMap::iterator it = data_.begin(); it != data_.end(); ++it) |
| to_delete.insert(it->second.first); |
| |
| for (std::set<char*>::iterator it = to_delete.begin(); it != to_delete.end(); |
| ++it) { |
| delete [] *it; |
| } |
| } |
| |
| void FormatMap::Insert(::Atom atom, char* data, size_t size) { |
| DCHECK(data_.find(atom) == data_.end()); |
| data_.insert(std::make_pair(atom, std::make_pair(data, size))); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // Represents a list of possible return types. Copy constructable. |
| class TargetList { |
| public: |
| typedef std::vector< ::Atom> AtomVector; |
| |
| TargetList(const AtomVector& target_list, X11AtomCache* atom_cache); |
| |
| bool ContainsText() const; |
| bool ContainsFormat(const Clipboard::FormatType& format_type) const; |
| bool ContainsAtom(::Atom atom) const; |
| |
| private: |
| AtomVector target_list_; |
| X11AtomCache* atom_cache_; |
| }; |
| |
| TargetList::TargetList(const AtomVector& target_list, |
| X11AtomCache* atom_cache) |
| : target_list_(target_list), |
| atom_cache_(atom_cache) { |
| } |
| |
| bool TargetList::ContainsText() const { |
| std::vector< ::Atom> atoms = GetTextAtomsFrom(atom_cache_); |
| for (std::vector< ::Atom>::const_iterator it = atoms.begin(); |
| it != atoms.end(); ++it) { |
| if (ContainsAtom(*it)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool TargetList::ContainsFormat( |
| const Clipboard::FormatType& format_type) const { |
| ::Atom atom = atom_cache_->GetAtom(format_type.ToString().c_str()); |
| return ContainsAtom(atom); |
| } |
| |
| bool TargetList::ContainsAtom(::Atom atom) const { |
| return find(target_list_.begin(), target_list_.end(), atom) |
| != target_list_.end(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // A holder for data with optional X11 deletion semantics. |
| class SelectionData { |
| public: |
| // |atom_cache| is still owned by caller. |
| explicit SelectionData(X11AtomCache* atom_cache); |
| ~SelectionData(); |
| |
| ::Atom type() const { return type_; } |
| char* data() const { return data_; } |
| size_t size() const { return size_; } |
| |
| void Set(::Atom type, char* data, size_t size, bool owned); |
| |
| // If |type_| is a string type, convert the data to UTF8 and return it. |
| std::string GetText() const; |
| |
| // Assigns the raw data to the string. |
| void AssignTo(std::string* result) const; |
| |
| private: |
| ::Atom type_; |
| char* data_; |
| size_t size_; |
| bool owned_; |
| |
| X11AtomCache* atom_cache_; |
| }; |
| |
| SelectionData::SelectionData(X11AtomCache* atom_cache) |
| : type_(None), |
| data_(NULL), |
| size_(0), |
| owned_(false), |
| atom_cache_(atom_cache) { |
| } |
| |
| SelectionData::~SelectionData() { |
| if (owned_) |
| XFree(data_); |
| } |
| |
| void SelectionData::Set(::Atom type, char* data, size_t size, bool owned) { |
| if (owned_) |
| XFree(data_); |
| |
| type_ = type; |
| data_ = data; |
| size_ = size; |
| owned_ = owned; |
| } |
| |
| std::string SelectionData::GetText() const { |
| if (type_ == atom_cache_->GetAtom(kUtf8String) || |
| type_ == atom_cache_->GetAtom(kText)) { |
| return std::string(data_, size_); |
| } else if (type_ == atom_cache_->GetAtom(kString)) { |
| std::string result; |
| base::ConvertToUtf8AndNormalize(std::string(data_, size_), |
| base::kCodepageLatin1, |
| &result); |
| return result; |
| } else { |
| // BTW, I looked at COMPOUND_TEXT, and there's no way we're going to |
| // support that. Yuck. |
| NOTREACHED(); |
| return std::string(); |
| } |
| } |
| |
| void SelectionData::AssignTo(std::string* result) const { |
| result->assign(data_, size_); |
| } |
| |
| } // namespace |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // I would love for the FormatType to really be a wrapper around an X11 ::Atom, |
| // but there are a few problems. Chromeos unit tests spawn a new X11 server for |
| // each test, so Atom numeric values don't persist across tests. We could still |
| // maybe deal with that if we didn't have static accessor methods everywhere. |
| |
| Clipboard::FormatType::FormatType() { |
| } |
| |
| Clipboard::FormatType::FormatType(const std::string& native_format) |
| : data_(native_format) { |
| } |
| |
| Clipboard::FormatType::~FormatType() { |
| } |
| |
| std::string Clipboard::FormatType::Serialize() const { |
| return data_; |
| } |
| |
| // static |
| Clipboard::FormatType Clipboard::FormatType::Deserialize( |
| const std::string& serialization) { |
| return FormatType(serialization); |
| } |
| |
| bool Clipboard::FormatType::Equals(const FormatType& other) const { |
| return data_ == other.data_; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Clipboard::AuraX11Details |
| |
| // Private implementation of our X11 integration. Keeps X11 headers out of the |
| // majority of chrome, which break badly. |
| class Clipboard::AuraX11Details : public base::MessagePumpDispatcher { |
| public: |
| AuraX11Details(); |
| ~AuraX11Details(); |
| |
| X11AtomCache* atom_cache() { return &atom_cache_; } |
| |
| // Returns the X11 type that we pass to various XSelection functions for the |
| // given buffer. |
| ::Atom LookupSelectionForBuffer(Buffer buffer) const; |
| |
| // Finds the FormatMap for the incoming selection atom. |
| FormatMap* LookupStorageForAtom(::Atom atom); |
| |
| // As we need to collect all the data types before we tell X11 that we own a |
| // particular selection, we create a temporary clipboard mapping that |
| // InsertMapping writes to. Then we commit it in TakeOwnershipOfSelection, |
| // where we save it in one of the clipboard data slots. |
| void CreateNewClipboardData(); |
| |
| // Inserts a mapping into clipboard_data_. |
| void InsertMapping(const std::string& key, char* data, size_t data_len); |
| |
| // Moves the temporary |clipboard_data_| to the long term data storage for |
| // |buffer|. |
| void TakeOwnershipOfSelection(Buffer buffer); |
| |
| // Returns the first of |types| offered by the current selection holder in |
| // |data_out|, or returns NULL if none of those types are available. |
| // |
| // If the selection holder is us, this call is synchronous and we pull |
| // the data out of |clipboard_selection_| or |primary_selection_|. If the |
| // selection holder is some other window, we spin up a nested message loop |
| // and do the asynchronous dance with whatever application is holding the |
| // selection. |
| scoped_ptr<SelectionData> RequestAndWaitForTypes( |
| Buffer buffer, |
| const std::vector< ::Atom>& types); |
| |
| // Retrieves the list of possible data types the current clipboard owner has. |
| // |
| // If the selection holder is us, this is synchronous, otherwise this runs a |
| // blocking message loop. |
| TargetList WaitAndGetTargetsList(Buffer buffer); |
| |
| // Does the work of requesting |target| from |selection_name|, spinning up |
| // the nested message loop, and reading the resulting data back. |out_data| |
| // is allocated with the X allocator and must be freed with |
| // XFree(). |out_data_bytes| is the length in machine chars, while |
| // |out_data_items| is the length in |out_type| items. |
| bool PerformBlockingConvertSelection(::Atom selection_name, |
| ::Atom target, |
| unsigned char** out_data, |
| size_t* out_data_bytes, |
| size_t* out_data_items, |
| ::Atom* out_type); |
| |
| // Returns a list of all text atoms that we handle. |
| std::vector< ::Atom> GetTextAtoms() const; |
| |
| // Returns a vector with a |format| converted to an X11 atom. |
| std::vector< ::Atom> GetAtomsForFormat(const Clipboard::FormatType& format); |
| |
| // Clears a certain data buffer. |
| void Clear(Buffer buffer); |
| |
| private: |
| // Called by Dispatch to handle specific types of events. |
| void HandleSelectionRequest(const XSelectionRequestEvent& event); |
| void HandleSelectionNotify(const XSelectionEvent& event); |
| void HandleSelectionClear(const XSelectionClearEvent& event); |
| void HandlePropertyNotify(const XPropertyEvent& event); |
| |
| // Overridden from base::MessagePumpDispatcher: |
| virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; |
| |
| // Temporary target map that we write to during DispatchObects. |
| scoped_ptr<FormatMap> clipboard_data_; |
| |
| // The current value of our clipboard and primary selections. These should be |
| // non-NULL when we own the selection. |
| scoped_ptr<FormatMap> clipboard_selection_; |
| scoped_ptr<FormatMap> primary_selection_; |
| |
| // Our X11 state. |
| Display* x_display_; |
| ::Window x_root_window_; |
| |
| // Input-only window used as a selection owner. |
| ::Window x_window_; |
| |
| // True if we're currently running a nested message loop, waiting for data to |
| // come back from the X server. |
| bool in_nested_loop_; |
| |
| // Data to the current XConvertSelection request. Used for error detection; |
| // we verify it on the return message. |
| ::Atom current_selection_; |
| ::Atom current_target_; |
| |
| // The property in the returning SelectNotify message is used to signal |
| // success. If None, our request failed somehow. If equal to the property |
| // atom that we sent in the XConvertSelection call, we can read that property |
| // on |x_window_| for the requested data. |
| ::Atom returned_property_; |
| |
| // Called to terminate the nested message loop. |
| base::Closure quit_closure_; |
| |
| X11AtomCache atom_cache_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AuraX11Details); |
| }; |
| |
| Clipboard::AuraX11Details::AuraX11Details() |
| : x_display_(GetXDisplay()), |
| x_root_window_(DefaultRootWindow(x_display_)), |
| in_nested_loop_(false), |
| atom_cache_(x_display_, kAtomsToCache) { |
| // We don't know all possible MIME types at compile time. |
| atom_cache_.allow_uncached_atoms(); |
| |
| x_window_ = XCreateWindow( |
| x_display_, x_root_window_, |
| -100, -100, 10, 10, // x, y, width, height |
| 0, // border width |
| CopyFromParent, // depth |
| InputOnly, |
| CopyFromParent, // visual |
| 0, |
| NULL); |
| XStoreName(x_display_, x_window_, "Chromium clipboard"); |
| XSelectInput(x_display_, x_window_, PropertyChangeMask); |
| |
| base::MessagePumpAuraX11::Current()->AddDispatcherForWindow(this, x_window_); |
| } |
| |
| Clipboard::AuraX11Details::~AuraX11Details() { |
| base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(x_window_); |
| |
| XDestroyWindow(x_display_, x_window_); |
| } |
| |
| ::Atom Clipboard::AuraX11Details::LookupSelectionForBuffer( |
| Buffer buffer) const { |
| if (buffer == BUFFER_STANDARD) |
| return atom_cache_.GetAtom(kClipboard); |
| else |
| return XA_PRIMARY; |
| } |
| |
| FormatMap* Clipboard::AuraX11Details::LookupStorageForAtom(::Atom atom) { |
| if (atom == XA_PRIMARY) |
| return primary_selection_.get(); |
| else if (atom == atom_cache_.GetAtom(kClipboard)) |
| return clipboard_selection_.get(); |
| else |
| return NULL; |
| } |
| |
| void Clipboard::AuraX11Details::CreateNewClipboardData() { |
| clipboard_data_.reset(new FormatMap); |
| } |
| |
| void Clipboard::AuraX11Details::InsertMapping(const std::string& key, |
| char* data, |
| size_t data_len) { |
| ::Atom atom_key = atom_cache_.GetAtom(key.c_str()); |
| clipboard_data_->Insert(atom_key, data, data_len); |
| } |
| |
| void Clipboard::AuraX11Details::TakeOwnershipOfSelection(Buffer buffer) { |
| // Tell the X server that we are now the selection owner. |
| ::Atom xselection = LookupSelectionForBuffer(buffer); |
| XSetSelectionOwner(x_display_, xselection, x_window_, CurrentTime); |
| |
| if (XGetSelectionOwner(x_display_, xselection) == x_window_) { |
| // The X server agrees that we are the selection owner. Commit our data. |
| if (buffer == BUFFER_STANDARD) |
| clipboard_selection_ = clipboard_data_.Pass(); |
| else |
| primary_selection_ = clipboard_data_.Pass(); |
| } |
| } |
| |
| scoped_ptr<SelectionData> Clipboard::AuraX11Details::RequestAndWaitForTypes( |
| Buffer buffer, |
| const std::vector< ::Atom>& types) { |
| ::Atom selection_name = LookupSelectionForBuffer(buffer); |
| if (XGetSelectionOwner(x_display_, selection_name) == x_window_) { |
| // We can local fastpath instead of playing the nested message loop game |
| // with the X server. |
| FormatMap* format_map = LookupStorageForAtom(selection_name); |
| DCHECK(format_map); |
| |
| for (std::vector< ::Atom>::const_iterator it = types.begin(); |
| it != types.end(); ++it) { |
| FormatMap::const_iterator format_map_it = format_map->find(*it); |
| if (format_map_it != format_map->end()) { |
| scoped_ptr<SelectionData> data_out(new SelectionData(&atom_cache_)); |
| data_out->Set(format_map_it->first, format_map_it->second.first, |
| format_map_it->second.second, false); |
| return data_out.Pass(); |
| } |
| } |
| } else { |
| TargetList targets = WaitAndGetTargetsList(buffer); |
| |
| for (std::vector< ::Atom>::const_iterator it = types.begin(); |
| it != types.end(); ++it) { |
| unsigned char* data = NULL; |
| size_t data_bytes = 0; |
| ::Atom type = None; |
| if (targets.ContainsAtom(*it) && |
| PerformBlockingConvertSelection(selection_name, |
| *it, |
| &data, |
| &data_bytes, |
| NULL, |
| &type) && |
| type == *it) { |
| scoped_ptr<SelectionData> data_out(new SelectionData(&atom_cache_)); |
| data_out->Set(type, (char*)data, data_bytes, true); |
| return data_out.Pass(); |
| } |
| } |
| } |
| |
| return scoped_ptr<SelectionData>(); |
| } |
| |
| TargetList Clipboard::AuraX11Details::WaitAndGetTargetsList( |
| Buffer buffer) { |
| ::Atom selection_name = LookupSelectionForBuffer(buffer); |
| std::vector< ::Atom> out; |
| if (XGetSelectionOwner(x_display_, selection_name) == x_window_) { |
| // We can local fastpath and return the list of local targets. |
| FormatMap* format_map = LookupStorageForAtom(selection_name); |
| DCHECK(format_map); |
| |
| for (FormatMap::const_iterator it = format_map->begin(); |
| it != format_map->end(); ++it) { |
| out.push_back(it->first); |
| } |
| } else { |
| unsigned char* data = NULL; |
| size_t out_data_items = 0; |
| ::Atom out_type = None; |
| |
| if (PerformBlockingConvertSelection(selection_name, |
| atom_cache_.GetAtom(kTargets), |
| &data, |
| NULL, |
| &out_data_items, |
| &out_type)) { |
| ::Atom* atom_array = reinterpret_cast< ::Atom*>(data); |
| for (size_t i = 0; i < out_data_items; ++i) |
| out.push_back(atom_array[i]); |
| |
| XFree(data); |
| } else { |
| // There was no target list. Most Java apps doesn't offer a TARGETS list, |
| // even though they AWT to. They will offer individual text types if you |
| // ask. If this is the case we attempt to make sense of the contents as |
| // text. This is pretty unfortunate since it means we have to actually |
| // copy the data to see if it is available, but at least this path |
| // shouldn't be hit for conforming programs. |
| std::vector< ::Atom> types = GetTextAtoms(); |
| for (std::vector< ::Atom>::const_iterator it = types.begin(); |
| it != types.end(); ++it) { |
| ::Atom type = None; |
| if (PerformBlockingConvertSelection(selection_name, |
| *it, |
| NULL, |
| NULL, |
| NULL, |
| &type) && |
| type == *it) { |
| out.push_back(*it); |
| } |
| } |
| } |
| } |
| |
| return TargetList(out, &atom_cache_); |
| } |
| |
| bool Clipboard::AuraX11Details::PerformBlockingConvertSelection( |
| ::Atom selection_name, |
| ::Atom target, |
| unsigned char** out_data, |
| size_t* out_data_bytes, |
| size_t* out_data_items, |
| ::Atom* out_type) { |
| // The name of the property we're asking to be set on |x_window_|. |
| ::Atom property_to_set = atom_cache_.GetAtom(kChromeSelection); |
| |
| XConvertSelection(x_display_, |
| selection_name, |
| target, |
| property_to_set, |
| x_window_, |
| CurrentTime); |
| |
| // Now that we've thrown our message off to the X11 server, we block waiting |
| // for a response. |
| MessageLoopForUI* loop = MessageLoopForUI::current(); |
| MessageLoop::ScopedNestableTaskAllower allow_nested(loop); |
| base::RunLoop run_loop(base::MessagePumpAuraX11::Current()); |
| |
| current_selection_ = selection_name; |
| current_target_ = target; |
| in_nested_loop_ = true; |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| in_nested_loop_ = false; |
| current_selection_ = None; |
| current_target_ = None; |
| |
| if (returned_property_ != property_to_set) |
| return false; |
| |
| // Retrieve the data from our window. |
| unsigned long nitems = 0; |
| unsigned long nbytes = 0; |
| Atom prop_type = None; |
| int prop_format = 0; |
| unsigned char* property_data = NULL; |
| if (XGetWindowProperty(x_display_, |
| x_window_, |
| returned_property_, |
| 0, 0x1FFFFFFF /* MAXINT32 / 4 */, False, |
| AnyPropertyType, &prop_type, &prop_format, |
| &nitems, &nbytes, &property_data) != Success) { |
| return false; |
| } |
| |
| if (prop_type == None) |
| return false; |
| |
| if (out_data) |
| *out_data = property_data; |
| |
| if (out_data_bytes) { |
| // So even though we should theoretically have nbytes (and we can't |
| // pass NULL there), we need to manually calculate the byte length here |
| // because nbytes always returns zero. |
| switch (prop_format) { |
| case 8: |
| *out_data_bytes = nitems; |
| break; |
| case 16: |
| *out_data_bytes = sizeof(short) * nitems; |
| break; |
| case 32: |
| *out_data_bytes = sizeof(long) * nitems; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| if (out_data_items) |
| *out_data_items = nitems; |
| |
| if (out_type) |
| *out_type = prop_type; |
| |
| return true; |
| } |
| |
| std::vector< ::Atom> Clipboard::AuraX11Details::GetTextAtoms() const { |
| return GetTextAtomsFrom(&atom_cache_); |
| } |
| |
| std::vector< ::Atom> Clipboard::AuraX11Details::GetAtomsForFormat( |
| const Clipboard::FormatType& format) { |
| std::vector< ::Atom> atoms; |
| atoms.push_back(atom_cache_.GetAtom(format.ToString().c_str())); |
| return atoms; |
| } |
| |
| void Clipboard::AuraX11Details::Clear(Buffer buffer) { |
| ::Atom selection_name = LookupSelectionForBuffer(buffer); |
| if (XGetSelectionOwner(x_display_, selection_name) == x_window_) |
| XSetSelectionOwner(x_display_, selection_name, None, CurrentTime); |
| |
| if (buffer == BUFFER_STANDARD) |
| clipboard_selection_.reset(); |
| else |
| primary_selection_.reset(); |
| } |
| |
| void Clipboard::AuraX11Details::HandleSelectionRequest( |
| const XSelectionRequestEvent& event) { |
| // Incrementally build our selection. By default this is a refusal, and we'll |
| // override the parts indicating success in the different cases. |
| XEvent reply; |
| reply.xselection.type = SelectionNotify; |
| reply.xselection.requestor = event.requestor; |
| reply.xselection.selection = event.selection; |
| reply.xselection.target = event.target; |
| reply.xselection.property = None; // Indicates failure |
| reply.xselection.time = event.time; |
| |
| // Get the proper selection. |
| FormatMap* format_map = LookupStorageForAtom(event.selection); |
| if (format_map) { |
| ::Atom targets_atom = atom_cache_.GetAtom(kTargets); |
| if (event.target == targets_atom) { |
| std::vector< ::Atom> targets; |
| targets.push_back(targets_atom); |
| for (FormatMap::const_iterator it = format_map->begin(); |
| it != format_map->end(); ++it) { |
| targets.push_back(it->first); |
| } |
| |
| XChangeProperty(x_display_, event.requestor, event.property, XA_ATOM, 32, |
| PropModeReplace, |
| reinterpret_cast<unsigned char*>(&targets.front()), |
| targets.size()); |
| reply.xselection.property = event.property; |
| } else if (event.target == atom_cache_.GetAtom(kMultiple)) { |
| // TODO(erg): Theoretically, the spec claims I'm supposed to handle the |
| // MULTIPLE case, but I haven't seen it in the wild yet. |
| NOTIMPLEMENTED(); |
| } else { |
| // Try to find the data type in map. |
| FormatMap::const_iterator it = format_map->find(event.target); |
| if (it != format_map->end()) { |
| XChangeProperty(x_display_, event.requestor, event.property, |
| event.target, 8, |
| PropModeReplace, |
| reinterpret_cast<unsigned char*>(it->second.first), |
| it->second.second); |
| reply.xselection.property = event.property; |
| } |
| // I would put error logging here, but GTK ignores TARGETS and spams us |
| // looking for its own internal types. |
| } |
| } else { |
| DLOG(ERROR) << "Requested on a selection we don't support: " |
| << XGetAtomName(x_display_, event.selection); |
| } |
| |
| // Send off the reply. |
| XSendEvent(x_display_, event.requestor, False, 0, &reply); |
| } |
| |
| void Clipboard::AuraX11Details::HandleSelectionNotify( |
| const XSelectionEvent& event) { |
| if (!in_nested_loop_) { |
| // This shouldn't happen; we're not waiting on the X server for data, but |
| // any client can send any message... |
| return; |
| } |
| |
| if (current_selection_ == event.selection && |
| current_target_ == event.target) { |
| returned_property_ = event.property; |
| } else { |
| // I am assuming that if some other client sent us a message after we've |
| // asked for data, but it's malformed, we should just treat as if they sent |
| // us an error message. |
| returned_property_ = None; |
| } |
| |
| quit_closure_.Run(); |
| } |
| |
| void Clipboard::AuraX11Details::HandleSelectionClear( |
| const XSelectionClearEvent& event) { |
| DLOG(ERROR) << "SelectionClear"; |
| |
| // TODO(erg): If we receive a SelectionClear event while we're handling data, |
| // we need to delay clearing. |
| } |
| |
| void Clipboard::AuraX11Details::HandlePropertyNotify( |
| const XPropertyEvent& event) { |
| // TODO(erg): Must handle PropertyNotify events on our |x_window_| as part |
| // of receiving data during paste. |
| } |
| |
| bool Clipboard::AuraX11Details::Dispatch(const base::NativeEvent& event) { |
| XEvent* xev = event; |
| |
| switch (xev->type) { |
| case SelectionRequest: |
| HandleSelectionRequest(xev->xselectionrequest); |
| break; |
| case SelectionNotify: |
| HandleSelectionNotify(xev->xselection); |
| break; |
| case SelectionClear: |
| HandleSelectionClear(xev->xselectionclear); |
| break; |
| case PropertyNotify: |
| HandlePropertyNotify(xev->xproperty); |
| break; |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Clipboard |
| |
| Clipboard::Clipboard() |
| : aurax11_details_(new AuraX11Details) { |
| DCHECK(CalledOnValidThread()); |
| } |
| |
| Clipboard::~Clipboard() { |
| DCHECK(CalledOnValidThread()); |
| |
| // TODO(erg): We need to do whatever the equivalent of |
| // gtk_clipboard_store(clipboard_) is here. When we shut down, we want the |
| // current selection to live on. |
| } |
| |
| void Clipboard::WriteObjectsImpl(Buffer buffer, |
| const ObjectMap& objects, |
| SourceTag tag) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(IsValidBuffer(buffer)); |
| |
| aurax11_details_->CreateNewClipboardData(); |
| for (ObjectMap::const_iterator iter = objects.begin(); |
| iter != objects.end(); ++iter) { |
| DispatchObject(static_cast<ObjectType>(iter->first), iter->second); |
| } |
| WriteSourceTag(tag); |
| aurax11_details_->TakeOwnershipOfSelection(buffer); |
| |
| if (buffer == BUFFER_STANDARD) { |
| ObjectMap::const_iterator text_iter = objects.find(CBF_TEXT); |
| if (text_iter != objects.end()) { |
| aurax11_details_->CreateNewClipboardData(); |
| const ObjectMapParam& char_vector = text_iter->second[0]; |
| WriteText(&char_vector.front(), char_vector.size()); |
| WriteSourceTag(tag); |
| aurax11_details_->TakeOwnershipOfSelection(BUFFER_SELECTION); |
| } |
| } |
| } |
| |
| bool Clipboard::IsFormatAvailable(const FormatType& format, |
| Buffer buffer) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(IsValidBuffer(buffer)); |
| |
| TargetList target_list = aurax11_details_->WaitAndGetTargetsList(buffer); |
| return target_list.ContainsFormat(format); |
| } |
| |
| void Clipboard::Clear(Buffer buffer) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(IsValidBuffer(buffer)); |
| aurax11_details_->Clear(buffer); |
| } |
| |
| void Clipboard::ReadAvailableTypes(Buffer buffer, std::vector<string16>* types, |
| bool* contains_filenames) const { |
| DCHECK(CalledOnValidThread()); |
| if (!types || !contains_filenames) { |
| NOTREACHED(); |
| return; |
| } |
| |
| TargetList target_list = aurax11_details_->WaitAndGetTargetsList(buffer); |
| |
| types->clear(); |
| |
| if (target_list.ContainsText()) |
| types->push_back(UTF8ToUTF16(kMimeTypeText)); |
| if (target_list.ContainsFormat(GetHtmlFormatType())) |
| types->push_back(UTF8ToUTF16(kMimeTypeHTML)); |
| if (target_list.ContainsFormat(GetRtfFormatType())) |
| types->push_back(UTF8ToUTF16(kMimeTypeRTF)); |
| if (target_list.ContainsFormat(GetBitmapFormatType())) |
| types->push_back(UTF8ToUTF16(kMimeTypePNG)); |
| *contains_filenames = false; |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, |
| aurax11_details_->GetAtomsForFormat(GetWebCustomDataFormatType()))); |
| if (!data.get()) |
| return; |
| |
| ReadCustomDataTypes(data->data(), data->size(), types); |
| } |
| |
| void Clipboard::ReadText(Buffer buffer, string16* result) const { |
| DCHECK(CalledOnValidThread()); |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, aurax11_details_->GetTextAtoms())); |
| if (data.get()) { |
| std::string text = data->GetText(); |
| *result = UTF8ToUTF16(text); |
| } |
| } |
| |
| void Clipboard::ReadAsciiText(Buffer buffer, std::string* result) const { |
| DCHECK(CalledOnValidThread()); |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, aurax11_details_->GetTextAtoms())); |
| if (data.get()) |
| result->assign(data->GetText()); |
| } |
| |
| // TODO(estade): handle different charsets. |
| // TODO(port): set *src_url. |
| void Clipboard::ReadHTML(Buffer buffer, |
| string16* markup, |
| std::string* src_url, |
| uint32* fragment_start, |
| uint32* fragment_end) const { |
| DCHECK(CalledOnValidThread()); |
| markup->clear(); |
| if (src_url) |
| src_url->clear(); |
| *fragment_start = 0; |
| *fragment_end = 0; |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, aurax11_details_->GetAtomsForFormat(GetHtmlFormatType()))); |
| if (!data.get()) |
| return; |
| |
| // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is |
| // UTF-16, otherwise assume UTF-8. |
| if (data->size() >= 2 && |
| reinterpret_cast<const uint16_t*>(data->data())[0] == 0xFEFF) { |
| markup->assign(reinterpret_cast<const uint16_t*>(data->data()) + 1, |
| (data->size() / 2) - 1); |
| } else { |
| UTF8ToUTF16(reinterpret_cast<const char*>(data->data()), data->size(), |
| markup); |
| } |
| |
| // If there is a terminating NULL, drop it. |
| if (!markup->empty() && markup->at(markup->length() - 1) == '\0') |
| markup->resize(markup->length() - 1); |
| |
| *fragment_start = 0; |
| DCHECK(markup->length() <= kuint32max); |
| *fragment_end = static_cast<uint32>(markup->length()); |
| } |
| |
| void Clipboard::ReadRTF(Buffer buffer, std::string* result) const { |
| DCHECK(CalledOnValidThread()); |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, aurax11_details_->GetAtomsForFormat(GetRtfFormatType()))); |
| if (data.get()) |
| data->AssignTo(result); |
| } |
| |
| SkBitmap Clipboard::ReadImage(Buffer buffer) const { |
| DCHECK(CalledOnValidThread()); |
| NOTIMPLEMENTED(); |
| return SkBitmap(); |
| } |
| |
| void Clipboard::ReadCustomData(Buffer buffer, |
| const string16& type, |
| string16* result) const { |
| DCHECK(CalledOnValidThread()); |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, |
| aurax11_details_->GetAtomsForFormat(GetWebCustomDataFormatType()))); |
| if (!data.get()) |
| return; |
| |
| ReadCustomDataForType(data->data(), data->size(), type, result); |
| } |
| |
| void Clipboard::ReadBookmark(string16* title, std::string* url) const { |
| DCHECK(CalledOnValidThread()); |
| // TODO(erg): This was left NOTIMPLEMENTED() in the gtk port too. |
| NOTIMPLEMENTED(); |
| } |
| |
| void Clipboard::ReadData(const FormatType& format, std::string* result) const { |
| ReadDataImpl(BUFFER_STANDARD, format, result); |
| } |
| |
| void Clipboard::ReadDataImpl(Buffer buffer, |
| const FormatType& format, |
| std::string* result) const { |
| DCHECK(CalledOnValidThread()); |
| |
| scoped_ptr<SelectionData> data(aurax11_details_->RequestAndWaitForTypes( |
| buffer, aurax11_details_->GetAtomsForFormat(format))); |
| if (data.get()) |
| data->AssignTo(result); |
| } |
| |
| Clipboard::SourceTag Clipboard::ReadSourceTag(Buffer buffer) const { |
| std::string result; |
| ReadDataImpl(buffer, GetSourceTagFormatType(), &result); |
| return Binary2SourceTag(result); |
| } |
| |
| uint64 Clipboard::GetSequenceNumber(Buffer buffer) { |
| DCHECK(CalledOnValidThread()); |
| if (buffer == BUFFER_STANDARD) |
| return SelectionChangeObserver::GetInstance()->clipboard_sequence_number(); |
| else |
| return SelectionChangeObserver::GetInstance()->primary_sequence_number(); |
| } |
| |
| void Clipboard::WriteText(const char* text_data, size_t text_len) { |
| char* data = new char[text_len]; |
| memcpy(data, text_data, text_len); |
| |
| aurax11_details_->InsertMapping(kMimeTypeText, data, text_len); |
| aurax11_details_->InsertMapping(kText, data, text_len); |
| aurax11_details_->InsertMapping(kString, data, text_len); |
| aurax11_details_->InsertMapping(kUtf8String, data, text_len); |
| } |
| |
| void Clipboard::WriteHTML(const char* markup_data, |
| size_t markup_len, |
| const char* url_data, |
| size_t url_len) { |
| // TODO(estade): We need to expand relative links with |url_data|. |
| static const char* html_prefix = "<meta http-equiv=\"content-type\" " |
| "content=\"text/html; charset=utf-8\">"; |
| size_t html_prefix_len = strlen(html_prefix); |
| size_t total_len = html_prefix_len + markup_len + 1; |
| |
| char* data = new char[total_len]; |
| snprintf(data, total_len, "%s", html_prefix); |
| memcpy(data + html_prefix_len, markup_data, markup_len); |
| // Some programs expect NULL-terminated data. See http://crbug.com/42624 |
| data[total_len - 1] = '\0'; |
| |
| aurax11_details_->InsertMapping(kMimeTypeHTML, data, total_len); |
| } |
| |
| void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { |
| WriteData(GetRtfFormatType(), rtf_data, data_len); |
| } |
| |
| void Clipboard::WriteBookmark(const char* title_data, |
| size_t title_len, |
| const char* url_data, |
| size_t url_len) { |
| // Write as a mozilla url (UTF16: URL, newline, title). |
| string16 url = UTF8ToUTF16(std::string(url_data, url_len) + "\n"); |
| string16 title = UTF8ToUTF16(std::string(title_data, title_len)); |
| int data_len = 2 * (title.length() + url.length()); |
| |
| char* data = new char[data_len]; |
| memcpy(data, url.data(), 2 * url.length()); |
| memcpy(data + 2 * url.length(), title.data(), 2 * title.length()); |
| aurax11_details_->InsertMapping(kMimeTypeMozillaURL, data, data_len); |
| } |
| |
| // Write an extra flavor that signifies WebKit was the last to modify the |
| // pasteboard. This flavor has no data. |
| void Clipboard::WriteWebSmartPaste() { |
| aurax11_details_->InsertMapping(kMimeTypeWebkitSmartPaste, NULL, 0); |
| } |
| |
| void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { |
| // TODO(erg): I'm not sure if we should be writting BMP data here or |
| // not. It's what the GTK port does, but I'm not sure it's the right thing to |
| // do. |
| NOTIMPLEMENTED(); |
| } |
| |
| void Clipboard::WriteData(const FormatType& format, |
| const char* data_data, |
| size_t data_len) { |
| // We assume that certain mapping types are only written by trusted code. |
| // Therefore we must upkeep their integrity. |
| if (format.Equals(GetBitmapFormatType())) |
| return; |
| char* data = new char[data_len]; |
| memcpy(data, data_data, data_len); |
| aurax11_details_->InsertMapping(format.ToString(), data, data_len); |
| } |
| |
| void Clipboard::WriteSourceTag(SourceTag tag) { |
| if (tag != SourceTag()) { |
| ObjectMapParam binary = SourceTag2Binary(tag); |
| WriteData(GetSourceTagFormatType(), &binary[0], binary.size()); |
| } |
| } |
| |
| // static |
| Clipboard::FormatType Clipboard::GetFormatType( |
| const std::string& format_string) { |
| return FormatType::Deserialize(format_string); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetUrlFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeURIList)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetUrlWFormatType() { |
| return GetUrlFormatType(); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeText)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { |
| return GetPlainTextFormatType(); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetFilenameFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeFilename)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() { |
| return Clipboard::GetFilenameFormatType(); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeHTML)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetRtfFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeRTF)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeBitmap)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebkitSmartPaste)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypeWebCustomData)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kMimeTypePepperCustomData)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetSourceTagFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kSourceTagType)); |
| return type; |
| } |
| |
| } // namespace ui |